diff options
539 files changed, 19378 insertions, 5122 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 497619ae0613..ad849002cca1 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -21,6 +21,7 @@ aconfig_declarations_group { java_aconfig_libraries: [ // !!! KEEP THIS LIST ALPHABETICAL !!! "aconfig_mediacodec_flags_java_lib", + "aconfig_settingslib_flags_java_lib", "aconfig_trade_in_mode_flags_java_lib", "android-sdk-flags-java", "android.adaptiveauth.flags-aconfig-java", @@ -1757,3 +1758,19 @@ cc_aconfig_library { ], min_sdk_version: "apex_inherit", } + +// Settings Lib +aconfig_declarations { + name: "aconfig_settingslib_flags", + package: "com.android.settingslib.flags", + container: "system", + srcs: [ + "packages/SettingsLib/aconfig/settingslib.aconfig", + ], +} + +java_aconfig_library { + name: "aconfig_settingslib_flags_java_lib", + aconfig_declarations: "aconfig_settingslib_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.bp b/Android.bp index 26d0d65f329c..48f0928f24d7 100644 --- a/Android.bp +++ b/Android.bp @@ -220,7 +220,7 @@ java_library { "android.hardware.contexthub-V1.0-java", "android.hardware.contexthub-V1.1-java", "android.hardware.contexthub-V1.2-java", - "android.hardware.contexthub-V3-java", + "android.hardware.contexthub-V4-java", "android.hardware.gnss-V1.0-java", "android.hardware.gnss-V2.1-java", "android.hardware.health-V1.0-java-constants", @@ -399,6 +399,7 @@ java_defaults { "com.android.sysprop.foldlockbehavior", "com.android.sysprop.view", "framework-internal-utils", + "dynamic_instrumentation_manager_aidl-java", // If MimeMap ever becomes its own APEX, then this dependency would need to be removed // in favor of an API stubs dependency in java_library "framework" below. "mimemap", diff --git a/api/Android.bp b/api/Android.bp index 0ac85e28de1a..6a0167766fd4 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -127,27 +127,54 @@ combined_apis { }), } +// Create a single file containing the latest released version of the whole +// Android public API. +java_genrule { + name: "android.api.merged.public.latest", + srcs: [ + ":android.api.combined.public.latest", + ], + out: ["public-latest.txt"], + tools: ["metalava"], + cmd: metalava_cmd + " merge-signatures --format=2.0 $(in) --out $(out)", +} + +// Make sure that the Android public API is compatible with the +// previously released public API. java_genrule { name: "frameworks-base-api-current-compat", srcs: [ - ":android.api.public.latest", + ":android.api.merged.public.latest", ":android-incompatibilities.api.public.latest", ":frameworks-base-api-current.txt", ], out: ["updated-baseline.txt"], tools: ["metalava"], cmd: metalava_cmd + - "--check-compatibility:api:released $(location :android.api.public.latest) " + + "--check-compatibility:api:released $(location :android.api.merged.public.latest) " + "--baseline:compatibility:released $(location :android-incompatibilities.api.public.latest) " + "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " + "$(location :frameworks-base-api-current.txt)", } +// Create a single file containing the latest released version of the whole +// Android system API. +java_genrule { + name: "android.api.merged.system.latest", + srcs: [ + ":android.api.combined.system.latest", + ], + out: ["system-latest.txt"], + tools: ["metalava"], + cmd: metalava_cmd + " merge-signatures --format=2.0 $(in) --out $(out)", +} + +// Make sure that the Android system API is compatible with the +// previously released system API. java_genrule { name: "frameworks-base-api-system-current-compat", srcs: [ - ":android.api.public.latest", - ":android.api.system.latest", + ":android.api.merged.system.latest", ":android-incompatibilities.api.system.latest", ":frameworks-base-api-current.txt", ":frameworks-base-api-system-current.txt", @@ -155,20 +182,31 @@ java_genrule { out: ["updated-baseline.txt"], tools: ["metalava"], cmd: metalava_cmd + - "--check-compatibility:api:released $(location :android.api.public.latest) " + - "--check-compatibility:api:released $(location :android.api.system.latest) " + + "--check-compatibility:api:released $(location :android.api.merged.system.latest) " + "--baseline:compatibility:released $(location :android-incompatibilities.api.system.latest) " + "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " + "$(location :frameworks-base-api-current.txt) " + "$(location :frameworks-base-api-system-current.txt)", } +// Create a single file containing the latest released version of the whole +// Android module-lib API. +java_genrule { + name: "android.api.merged.module-lib.latest", + srcs: [ + ":android.api.combined.module-lib.latest", + ], + out: ["module-lib-latest.txt"], + tools: ["metalava"], + cmd: metalava_cmd + " merge-signatures --format=2.0 $(in) --out $(out)", +} + +// Make sure that the Android module-lib API is compatible with the +// previously released module-lib API. java_genrule { name: "frameworks-base-api-module-lib-current-compat", srcs: [ - ":android.api.public.latest", - ":android.api.system.latest", - ":android.api.module-lib.latest", + ":android.api.merged.module-lib.latest", ":android-incompatibilities.api.module-lib.latest", ":frameworks-base-api-current.txt", ":frameworks-base-api-system-current.txt", @@ -177,9 +215,7 @@ java_genrule { out: ["updated-baseline.txt"], tools: ["metalava"], cmd: metalava_cmd + - "--check-compatibility:api:released $(location :android.api.public.latest) " + - "--check-compatibility:api:released $(location :android.api.system.latest) " + - "--check-compatibility:api:released $(location :android.api.module-lib.latest) " + + "--check-compatibility:api:released $(location :android.api.merged.module-lib.latest) " + "--baseline:compatibility:released $(location :android-incompatibilities.api.module-lib.latest) " + "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " + "$(location :frameworks-base-api-current.txt) " + diff --git a/core/api/current.txt b/core/api/current.txt index ead655426bf9..f03ef8c10d88 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -263,6 +263,7 @@ package android { field public static final String READ_SMS = "android.permission.READ_SMS"; field public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS"; field public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS"; + field @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final String READ_SYSTEM_PREFERENCES = "android.permission.READ_SYSTEM_PREFERENCES"; field public static final String READ_VOICEMAIL = "com.android.voicemail.permission.READ_VOICEMAIL"; field public static final String REBOOT = "android.permission.REBOOT"; field public static final String RECEIVE_BOOT_COMPLETED = "android.permission.RECEIVE_BOOT_COMPLETED"; @@ -313,6 +314,7 @@ package android { field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW"; field public static final String TRANSMIT_IR = "android.permission.TRANSMIT_IR"; field public static final String TURN_SCREEN_ON = "android.permission.TURN_SCREEN_ON"; + field @FlaggedApi("android.app.enable_tv_implicit_enter_pip_restriction") public static final String TV_IMPLICIT_ENTER_PIP = "android.permission.TV_IMPLICIT_ENTER_PIP"; field public static final String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT"; field public static final String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS"; field public static final String UPDATE_PACKAGES_WITHOUT_USER_ACTION = "android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION"; @@ -334,6 +336,7 @@ package android { field public static final String WRITE_SECURE_SETTINGS = "android.permission.WRITE_SECURE_SETTINGS"; field public static final String WRITE_SETTINGS = "android.permission.WRITE_SETTINGS"; field public static final String WRITE_SYNC_SETTINGS = "android.permission.WRITE_SYNC_SETTINGS"; + field @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final String WRITE_SYSTEM_PREFERENCES = "android.permission.WRITE_SYSTEM_PREFERENCES"; field public static final String WRITE_VOICEMAIL = "com.android.voicemail.permission.WRITE_VOICEMAIL"; } @@ -475,6 +478,8 @@ package android { field public static final int alpha = 16843551; // 0x101031f field public static final int alphabeticModifiers = 16844110; // 0x101054e field public static final int alphabeticShortcut = 16843235; // 0x10101e3 + field @FlaggedApi("android.content.pm.change_launcher_badging") public static final int alternateLauncherIcons; + field @FlaggedApi("android.content.pm.change_launcher_badging") public static final int alternateLauncherLabels; field public static final int alwaysDrawnWithCache = 16842991; // 0x10100ef field public static final int alwaysRetainTaskState = 16843267; // 0x1010203 field @Deprecated public static final int amPmBackgroundColor = 16843941; // 0x10104a5 @@ -8787,8 +8792,31 @@ package android.app.admin { package android.app.appfunctions { + @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionException extends java.lang.Exception implements android.os.Parcelable { + ctor public AppFunctionException(int, @Nullable String); + ctor public AppFunctionException(int, @Nullable String, @NonNull android.os.Bundle); + method public int describeContents(); + method public int getErrorCategory(); + method public int getErrorCode(); + method @Nullable public String getErrorMessage(); + method @NonNull public android.os.Bundle getExtras(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.AppFunctionException> CREATOR; + field public static final int ERROR_APP_UNKNOWN_ERROR = 3000; // 0xbb8 + field public static final int ERROR_CANCELLED = 2001; // 0x7d1 + field public static final int ERROR_CATEGORY_APP = 3; // 0x3 + field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1 + field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2 + 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_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 + } + @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager { - method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); + method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>); method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>); @@ -8800,7 +8828,7 @@ package android.app.appfunctions { @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service { ctor public AppFunctionService(); method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); + method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>); field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } @@ -8822,30 +8850,14 @@ package android.app.appfunctions { } @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionResponse implements android.os.Parcelable { + ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument); + ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle); method public int describeContents(); - method public int getErrorCategory(); - method @Nullable public String getErrorMessage(); method @NonNull public android.os.Bundle getExtras(); - method public int getResultCode(); method @NonNull public android.app.appsearch.GenericDocument getResultDocument(); - method public boolean isSuccess(); - method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle); - method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR; - field public static final int ERROR_CATEGORY_APP = 3; // 0x3 - field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1 - field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2 - field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 - field public static final String PROPERTY_RETURN_VALUE = "returnValue"; - field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8 - field public static final int RESULT_CANCELLED = 2001; // 0x7d1 - field public static final int RESULT_DENIED = 1000; // 0x3e8 - field public static final int RESULT_DISABLED = 1002; // 0x3ea - field public static final int RESULT_FUNCTION_NOT_FOUND = 1003; // 0x3eb - field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9 - field public static final int RESULT_OK = 0; // 0x0 - field public static final int RESULT_SYSTEM_ERROR = 2000; // 0x7d0 + field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; } } @@ -8868,6 +8880,7 @@ package android.app.assist { method public void setWebUri(android.net.Uri); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.assist.AssistContent> CREATOR; + field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXTRA_APP_FUNCTION_DATA = "android.app.assist.extra.APP_FUNCTION_DATA"; } public class AssistStructure implements android.os.Parcelable { @@ -19777,6 +19790,9 @@ package android.hardware.camera2 { field public static final int EDGE_MODE_HIGH_QUALITY = 2; // 0x2 field public static final int EDGE_MODE_OFF = 0; // 0x0 field public static final int EDGE_MODE_ZERO_SHUTTER_LAG = 3; // 0x3 + field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") public static final int EXTENSION_NIGHT_MODE_INDICATOR_OFF = 1; // 0x1 + field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") public static final int EXTENSION_NIGHT_MODE_INDICATOR_ON = 2; // 0x2 + field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") public static final int EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN = 0; // 0x0 field public static final int FLASH_MODE_OFF = 0; // 0x0 field public static final int FLASH_MODE_SINGLE = 1; // 0x1 field public static final int FLASH_MODE_TORCH = 2; // 0x2 @@ -20076,6 +20092,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> DISTORTION_CORRECTION_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EDGE_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_CURRENT_TYPE; + field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_NIGHT_MODE_INDICATOR; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_STRENGTH; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_STATE; @@ -23973,6 +23990,7 @@ package android.media { field public static final String KEY_MPEGH_COMPATIBLE_SETS = "mpegh-compatible-sets"; field public static final String KEY_MPEGH_PROFILE_LEVEL_INDICATION = "mpegh-profile-level-indication"; field public static final String KEY_MPEGH_REFERENCE_CHANNEL_LAYOUT = "mpegh-reference-channel-layout"; + field @FlaggedApi("android.media.codec.num_input_slots") public static final String KEY_NUM_SLOTS = "num-slots"; field public static final String KEY_OPERATING_RATE = "operating-rate"; field public static final String KEY_OUTPUT_REORDER_DEPTH = "output-reorder-depth"; field public static final String KEY_PCM_ENCODING = "pcm-encoding"; @@ -40371,7 +40389,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyProtection.Builder setUserPresenceRequired(boolean); } - @FlaggedApi("android.security.keystore_grant_api") public class KeyStoreManager { + @FlaggedApi("android.security.keystore_grant_api") public final class KeyStoreManager { method @NonNull public java.util.List<java.security.cert.X509Certificate> getGrantedCertificateChainFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException; method @NonNull public java.security.Key getGrantedKeyFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException; method @NonNull public java.security.KeyPair getGrantedKeyPairFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException; @@ -41999,6 +42017,174 @@ package android.service.restrictions { } +package android.service.settings.preferences { + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class GetValueRequest implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public String getPreferenceKey(); + method @NonNull public String getScreenKey(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.GetValueRequest> CREATOR; + } + + public static final class GetValueRequest.Builder { + ctor public GetValueRequest.Builder(@NonNull String, @NonNull String); + method @NonNull public android.service.settings.preferences.GetValueRequest build(); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class GetValueResult implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.service.settings.preferences.SettingsPreferenceMetadata getMetadata(); + method public int getResultCode(); + method @Nullable public android.service.settings.preferences.SettingsPreferenceValue getValue(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.GetValueResult> CREATOR; + field public static final int RESULT_DISALLOW = 4; // 0x4 + field public static final int RESULT_INTERNAL_ERROR = 6; // 0x6 + field public static final int RESULT_INVALID_REQUEST = 5; // 0x5 + field public static final int RESULT_OK = 0; // 0x0 + field public static final int RESULT_REQUIRE_APP_PERMISSION = 3; // 0x3 + field public static final int RESULT_UNAVAILABLE = 2; // 0x2 + field public static final int RESULT_UNSUPPORTED = 1; // 0x1 + } + + public static final class GetValueResult.Builder { + ctor public GetValueResult.Builder(int); + method @NonNull public android.service.settings.preferences.GetValueResult build(); + method @NonNull public android.service.settings.preferences.GetValueResult.Builder setMetadata(@Nullable android.service.settings.preferences.SettingsPreferenceMetadata); + method @NonNull public android.service.settings.preferences.GetValueResult.Builder setValue(@Nullable android.service.settings.preferences.SettingsPreferenceValue); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class MetadataRequest implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.MetadataRequest> CREATOR; + } + + public static final class MetadataRequest.Builder { + ctor public MetadataRequest.Builder(); + method @NonNull public android.service.settings.preferences.MetadataRequest build(); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class MetadataResult implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<android.service.settings.preferences.SettingsPreferenceMetadata> getMetadataList(); + method public int getResultCode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.MetadataResult> CREATOR; + field public static final int RESULT_INTERNAL_ERROR = 2; // 0x2 + field public static final int RESULT_OK = 0; // 0x0 + field public static final int RESULT_UNSUPPORTED = 1; // 0x1 + } + + public static final class MetadataResult.Builder { + ctor public MetadataResult.Builder(int); + method @NonNull public android.service.settings.preferences.MetadataResult build(); + method @NonNull public android.service.settings.preferences.MetadataResult.Builder setMetadataList(@NonNull java.util.List<android.service.settings.preferences.SettingsPreferenceMetadata>); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SetValueRequest implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public String getPreferenceKey(); + method @NonNull public android.service.settings.preferences.SettingsPreferenceValue getPreferenceValue(); + method @NonNull public String getScreenKey(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SetValueRequest> CREATOR; + } + + public static final class SetValueRequest.Builder { + ctor public SetValueRequest.Builder(@NonNull String, @NonNull String, @NonNull android.service.settings.preferences.SettingsPreferenceValue); + method @NonNull public android.service.settings.preferences.SetValueRequest build(); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SetValueResult implements android.os.Parcelable { + method public int describeContents(); + method public int getResultCode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SetValueResult> CREATOR; + field public static final int RESULT_DISABLED = 2; // 0x2 + field public static final int RESULT_DISALLOW = 7; // 0x7 + field public static final int RESULT_INTERNAL_ERROR = 9; // 0x9 + field public static final int RESULT_INVALID_REQUEST = 8; // 0x8 + field public static final int RESULT_OK = 0; // 0x0 + field public static final int RESULT_REQUIRE_APP_PERMISSION = 5; // 0x5 + field public static final int RESULT_REQUIRE_USER_CONSENT = 6; // 0x6 + field public static final int RESULT_RESTRICTED = 3; // 0x3 + field public static final int RESULT_UNAVAILABLE = 4; // 0x4 + field public static final int RESULT_UNSUPPORTED = 1; // 0x1 + } + + public static final class SetValueResult.Builder { + ctor public SetValueResult.Builder(int); + method @NonNull public android.service.settings.preferences.SetValueResult build(); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SettingsPreferenceMetadata implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getBreadcrumbs(); + method @NonNull public android.os.Bundle getExtras(); + method @NonNull public String getKey(); + method @Nullable public android.app.PendingIntent getLaunchIntent(); + method @NonNull public java.util.List<java.lang.String> getReadPermissions(); + method @NonNull public String getScreenKey(); + method @Nullable public String getSummary(); + method @Nullable public String getTitle(); + method @NonNull public java.util.List<java.lang.String> getWritePermissions(); + method public int getWriteSensitivity(); + method public boolean isAvailable(); + method public boolean isEnabled(); + method public boolean isRestricted(); + method public boolean isWritable(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SettingsPreferenceMetadata> CREATOR; + field public static final int INTENT_ONLY = 2; // 0x2 + field public static final int NOT_SENSITIVE = 0; // 0x0 + field public static final int SENSITIVE = 1; // 0x1 + } + + public static final class SettingsPreferenceMetadata.Builder { + ctor public SettingsPreferenceMetadata.Builder(@NonNull String, @NonNull String); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata build(); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setAvailable(boolean); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setBreadcrumbs(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setEnabled(boolean); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setLaunchIntent(@Nullable android.app.PendingIntent); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setReadPermissions(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setRestricted(boolean); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setSummary(@Nullable String); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setTitle(@Nullable String); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setWritable(boolean); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setWritePermissions(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setWriteSensitivity(int); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SettingsPreferenceValue implements android.os.Parcelable { + method public int describeContents(); + method public boolean getBooleanValue(); + method public double getDoubleValue(); + method public long getLongValue(); + method @Nullable public String getStringValue(); + method public int getType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SettingsPreferenceValue> CREATOR; + field public static final int TYPE_BOOLEAN = 0; // 0x0 + field public static final int TYPE_DOUBLE = 2; // 0x2 + field public static final int TYPE_LONG = 1; // 0x1 + field public static final int TYPE_STRING = 3; // 0x3 + } + + public static final class SettingsPreferenceValue.Builder { + ctor public SettingsPreferenceValue.Builder(int); + method @NonNull public android.service.settings.preferences.SettingsPreferenceValue build(); + method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setBooleanValue(boolean); + method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setDoubleValue(double); + method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setLongValue(long); + method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setStringValue(@Nullable String); + } + +} + package android.service.textservice { public abstract class SpellCheckerService extends android.app.Service { @@ -44104,6 +44290,8 @@ package android.telephony { field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; field public static final int CARRIER_NR_AVAILABILITY_NSA = 1; // 0x1 field public static final int CARRIER_NR_AVAILABILITY_SA = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int CARRIER_ROAMING_NTN_CONNECT_MANUAL = 1; // 0x1 field public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_ONLY = 0; // 0x0 field public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_WITH_BRANDING = 1; // 0x1 field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe @@ -44174,11 +44362,14 @@ package android.telephony { field public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY = "carrier_nr_availabilities_int_array"; field public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL = "carrier_provisions_wifi_merged_networks_bool"; field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT = "carrier_roaming_ntn_connect_type_int"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT = "carrier_roaming_ntn_emergency_call_to_satellite_handover_type_int"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY = "carrier_roaming_satellite_default_services_int_array"; field public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array"; field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array"; field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string"; field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT = "carrier_supported_satellite_notification_hysteresis_sec_int"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle"; field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool"; field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; @@ -44229,6 +44420,7 @@ package android.telephony { field public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array"; field public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool"; field public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL = "disable_charge_indication_bool"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL = "disable_dun_apn_while_roaming_with_preset_apn_bool"; field public static final String KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL = "disable_supplementary_services_in_airplane_mode_bool"; field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array"; field public static final String KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL = "display_call_strength_indicator_bool"; @@ -44241,6 +44433,8 @@ package android.telephony { field public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool"; field public static final String KEY_EDITABLE_WFC_MODE_BOOL = "editable_wfc_mode_bool"; field public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL = "editable_wfc_roaming_mode_bool"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT = "emergency_call_to_satellite_t911_handover_timeout_millis_int"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL = "emergency_messaging_supported_bool"; field public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT = "emergency_notification_delay_int"; field public static final String KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY = "emergency_number_prefix_string_array"; field public static final String KEY_ENABLE_CROSS_SIM_CALLING_ON_OPPORTUNISTIC_DATA_BOOL = "enable_cross_sim_calling_on_opportunistic_data_bool"; @@ -44287,6 +44481,7 @@ package android.telephony { field public static final String KEY_MMS_MAX_IMAGE_HEIGHT_INT = "maxImageHeight"; field public static final String KEY_MMS_MAX_IMAGE_WIDTH_INT = "maxImageWidth"; field public static final String KEY_MMS_MAX_MESSAGE_SIZE_INT = "maxMessageSize"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT = "mms_max_ntn_payload_size_bytes_int"; field public static final String KEY_MMS_MESSAGE_TEXT_MAX_SIZE_INT = "maxMessageTextSize"; field public static final String KEY_MMS_MMS_DELIVERY_REPORT_ENABLED_BOOL = "enableMMSDeliveryReports"; field public static final String KEY_MMS_MMS_ENABLED_BOOL = "enabledMMS"; @@ -44325,6 +44520,7 @@ package android.telephony { field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT = "opportunistic_network_exit_threshold_rssnr_int"; field public static final String KEY_OPPORTUNISTIC_NETWORK_MAX_BACKOFF_TIME_LONG = "opportunistic_network_max_backoff_time_long"; field public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL = "override_wfc_roaming_mode_while_using_ntn_bool"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT = "parameters_used_for_ntn_lte_signal_bar_int"; field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool"; field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; @@ -44343,6 +44539,7 @@ package android.telephony { field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string"; field public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY = "read_only_apn_fields_string_array"; field public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = "read_only_apn_types_string_array"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_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"; field public static final String KEY_RTT_AUTO_UPGRADE_BOOL = "rtt_auto_upgrade_bool"; @@ -44354,13 +44551,18 @@ package android.telephony { field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int"; + field @FlaggedApi("com.android.internal.telephony.flags.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_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"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING = "satellite_information_redirect_url_string"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_NIDD_APN_NAME_STRING = "satellite_nidd_apn_name_string"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_esos_inactivity_timeout_sec_int"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_p2p_sms_inactivity_timeout_sec_int"; 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 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"; @@ -44430,6 +44632,9 @@ package android.telephony { field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool"; field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool"; field public static final String REMOVE_GROUP_UUID_STRING = "00000000-0000-0000-0000-000000000000"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_DATA_SUPPORT_ALL = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED = 0; // 0x0 field public static final int SERVICE_CLASS_NONE = 0; // 0x0 field public static final int SERVICE_CLASS_VOICE = 1; // 0x1 field public static final int USSD_OVER_CS_ONLY = 2; // 0x2 @@ -51799,6 +52004,7 @@ package android.view { field public static final int KEYCODE_CHANNEL_DOWN = 167; // 0xa7 field public static final int KEYCODE_CHANNEL_UP = 166; // 0xa6 field public static final int KEYCODE_CLEAR = 28; // 0x1c + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_CLOSE = 321; // 0x141 field public static final int KEYCODE_COMMA = 55; // 0x37 field public static final int KEYCODE_CONTACTS = 207; // 0xcf field public static final int KEYCODE_COPY = 278; // 0x116 @@ -51811,6 +52017,8 @@ package android.view { field public static final int KEYCODE_DEMO_APP_2 = 302; // 0x12e field public static final int KEYCODE_DEMO_APP_3 = 303; // 0x12f field public static final int KEYCODE_DEMO_APP_4 = 304; // 0x130 + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_DICTATE = 319; // 0x13f + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_DO_NOT_DISTURB = 322; // 0x142 field public static final int KEYCODE_DPAD_CENTER = 23; // 0x17 field public static final int KEYCODE_DPAD_DOWN = 20; // 0x14 field public static final int KEYCODE_DPAD_DOWN_LEFT = 269; // 0x10d @@ -51823,7 +52031,7 @@ package android.view { field public static final int KEYCODE_DVR = 173; // 0xad field public static final int KEYCODE_E = 33; // 0x21 field public static final int KEYCODE_EISU = 212; // 0xd4 - field @FlaggedApi("com.android.hardware.input.emoji_and_screenshot_keycodes_available") public static final int KEYCODE_EMOJI_PICKER = 317; // 0x13d + field public static final int KEYCODE_EMOJI_PICKER = 317; // 0x13d field public static final int KEYCODE_ENDCALL = 6; // 0x6 field public static final int KEYCODE_ENTER = 66; // 0x42 field public static final int KEYCODE_ENVELOPE = 65; // 0x41 @@ -51835,7 +52043,19 @@ package android.view { field public static final int KEYCODE_F10 = 140; // 0x8c field public static final int KEYCODE_F11 = 141; // 0x8d field public static final int KEYCODE_F12 = 142; // 0x8e + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F13 = 326; // 0x146 + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F14 = 327; // 0x147 + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F15 = 328; // 0x148 + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F16 = 329; // 0x149 + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F17 = 330; // 0x14a + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F18 = 331; // 0x14b + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F19 = 332; // 0x14c field public static final int KEYCODE_F2 = 132; // 0x84 + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F20 = 333; // 0x14d + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F21 = 334; // 0x14e + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F22 = 335; // 0x14f + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F23 = 336; // 0x150 + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F24 = 337; // 0x151 field public static final int KEYCODE_F3 = 133; // 0x85 field public static final int KEYCODE_F4 = 134; // 0x86 field public static final int KEYCODE_F5 = 135; // 0x87 @@ -51850,6 +52070,7 @@ package android.view { field public static final int KEYCODE_FOCUS = 80; // 0x50 field public static final int KEYCODE_FORWARD = 125; // 0x7d field public static final int KEYCODE_FORWARD_DEL = 112; // 0x70 + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_FULLSCREEN = 325; // 0x145 field public static final int KEYCODE_FUNCTION = 119; // 0x77 field public static final int KEYCODE_G = 35; // 0x23 field public static final int KEYCODE_GRAVE = 68; // 0x44 @@ -51873,6 +52094,7 @@ package android.view { field public static final int KEYCODE_LANGUAGE_SWITCH = 204; // 0xcc field public static final int KEYCODE_LAST_CHANNEL = 229; // 0xe5 field public static final int KEYCODE_LEFT_BRACKET = 71; // 0x47 + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_LOCK = 324; // 0x144 field public static final int KEYCODE_M = 41; // 0x29 field public static final int KEYCODE_MACRO_1 = 313; // 0x139 field public static final int KEYCODE_MACRO_2 = 314; // 0x13a @@ -51910,6 +52132,7 @@ package android.view { field public static final int KEYCODE_NAVIGATE_NEXT = 261; // 0x105 field public static final int KEYCODE_NAVIGATE_OUT = 263; // 0x107 field public static final int KEYCODE_NAVIGATE_PREVIOUS = 260; // 0x104 + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_NEW = 320; // 0x140 field public static final int KEYCODE_NOTIFICATION = 83; // 0x53 field public static final int KEYCODE_NUM = 78; // 0x4e field public static final int KEYCODE_NUMPAD_0 = 144; // 0x90 @@ -51944,6 +52167,7 @@ package android.view { field public static final int KEYCODE_PLUS = 81; // 0x51 field public static final int KEYCODE_POUND = 18; // 0x12 field public static final int KEYCODE_POWER = 26; // 0x1a + field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_PRINT = 323; // 0x143 field public static final int KEYCODE_PROFILE_SWITCH = 288; // 0x120 field public static final int KEYCODE_PROG_BLUE = 186; // 0xba field public static final int KEYCODE_PROG_GREEN = 184; // 0xb8 @@ -51956,7 +52180,7 @@ package android.view { field public static final int KEYCODE_RIGHT_BRACKET = 72; // 0x48 field public static final int KEYCODE_RO = 217; // 0xd9 field public static final int KEYCODE_S = 47; // 0x2f - field @FlaggedApi("com.android.hardware.input.emoji_and_screenshot_keycodes_available") public static final int KEYCODE_SCREENSHOT = 318; // 0x13e + field public static final int KEYCODE_SCREENSHOT = 318; // 0x13e field public static final int KEYCODE_SCROLL_LOCK = 116; // 0x74 field public static final int KEYCODE_SEARCH = 84; // 0x54 field public static final int KEYCODE_SEMICOLON = 74; // 0x4a diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 2a01ca082832..8954f8e70157 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -138,6 +138,7 @@ package android { field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS"; field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE"; field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT"; + field @FlaggedApi("com.android.art.flags.executable_method_file_offsets") public static final String DYNAMIC_INSTRUMENTATION = "android.permission.DYNAMIC_INSTRUMENTATION"; field @FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission") public static final String EMBED_ANY_APP_IN_UNTRUSTED_MODE = "android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE"; field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES"; field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED"; @@ -389,6 +390,7 @@ package android { field public static final String START_CROSS_PROFILE_ACTIVITIES = "android.permission.START_CROSS_PROFILE_ACTIVITIES"; field public static final String START_REVIEW_PERMISSION_DECISIONS = "android.permission.START_REVIEW_PERMISSION_DECISIONS"; field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS"; + field @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final String START_VIBRATION_SESSIONS = "android.permission.START_VIBRATION_SESSIONS"; field public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE"; field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES"; field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"; @@ -705,6 +707,7 @@ package android.app { field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images"; field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video"; field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected"; + field @FlaggedApi("android.permission.flags.platform_oxygen_saturation_enabled") public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation"; field @FlaggedApi("android.permission.flags.platform_skin_temperature_enabled") public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature"; field public static final String OPSTR_READ_WRITE_HEALTH_DATA = "android:read_write_health_data"; field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio"; @@ -11660,8 +11663,11 @@ package android.os { public abstract class Vibrator { method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener); method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener); + method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public boolean areVendorEffectsSupported(); + method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public boolean areVendorSessionsSupported(); method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public boolean isVibrating(); method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void removeVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener); + method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") @RequiresPermission(allOf={android.Manifest.permission.VIBRATE, android.Manifest.permission.VIBRATE_VENDOR_EFFECTS, android.Manifest.permission.START_VIBRATION_SESSIONS}) public void startVendorSession(@NonNull android.os.VibrationAttributes, @Nullable String, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.vibrator.VendorVibrationSession.Callback); } public static interface Vibrator.OnVibratorStateChangedListener { @@ -11813,6 +11819,28 @@ package android.os.storage { } +package android.os.vibrator { + + @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public final class VendorVibrationSession implements java.lang.AutoCloseable { + method public void cancel(); + method public void close(); + method @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(@NonNull android.os.VibrationEffect, @Nullable String); + field public static final int STATUS_CANCELED = 4; // 0x4 + field public static final int STATUS_IGNORED = 2; // 0x2 + field public static final int STATUS_SUCCESS = 1; // 0x1 + field public static final int STATUS_UNKNOWN = 0; // 0x0 + field public static final int STATUS_UNKNOWN_ERROR = 5; // 0x5 + field public static final int STATUS_UNSUPPORTED = 3; // 0x3 + } + + public static interface VendorVibrationSession.Callback { + method public void onFinished(int); + method public void onFinishing(); + method public void onStarted(@NonNull android.os.vibrator.VendorVibrationSession); + } + +} + package android.os.vibrator.persistence { @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class ParsedVibration { @@ -18305,7 +18333,12 @@ package android.telephony.satellite { method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForModemStateChanged(@NonNull android.telephony.satellite.SatelliteModemStateCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback); + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS = 7; // 0x7 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5; // 0x5 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP = 4; // 0x4 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_SMS = 6; // 0x6 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_UNKNOWN = 0; // 0x0 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; // 0x2 @@ -18325,6 +18358,7 @@ package android.telephony.satellite { field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0 field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; // 0x2 field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER = 0; // 0x0 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6 @@ -18338,6 +18372,8 @@ package android.telephony.satellite { field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_MODEM_STATE_DISABLING_SATELLITE = 9; // 0x9 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_MODEM_STATE_ENABLING_SATELLITE = 8; // 0x8 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6 @@ -18345,11 +18381,16 @@ package android.telephony.satellite { field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_DISABLE_IN_PROGRESS = 28; // 0x1c + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS = 27; // 0x1b + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_ENABLE_IN_PROGRESS = 29; // 0x1d field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ERROR = 1; // 0x1 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23; // 0x17 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_LOCATION_DISABLED = 25; // 0x19 + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_LOCATION_NOT_AVAILABLE = 26; // 0x1a field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_TIMEOUT = 24; // 0x18 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 117351943587..8dd12172fdc5 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3729,7 +3729,7 @@ package android.view { method public final int getDisplayId(); method public final void setDisplayId(int); field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800 - field public static final int LAST_KEYCODE = 318; // 0x13e + field public static final int LAST_KEYCODE = 337; // 0x151 } public final class KeyboardShortcutGroup implements android.os.Parcelable { diff --git a/core/java/Android.bp b/core/java/Android.bp index 71623c566501..cf5ebbaa37b4 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -28,6 +28,7 @@ filegroup { exclude_srcs: [ "android/os/*MessageQueue/**/*.java", "android/ranging/**/*.java", + ":dynamic_instrumentation_manager_aidl_sources", ], visibility: ["//frameworks/base"], } @@ -120,6 +121,17 @@ filegroup { } filegroup { + name: "dynamic_instrumentation_manager_aidl_sources", + srcs: ["android/os/instrumentation/*.aidl"], +} + +aidl_interface { + name: "dynamic_instrumentation_manager_aidl", + srcs: [":dynamic_instrumentation_manager_aidl_sources"], + unstable: true, +} + +filegroup { name: "framework-internal-display-sources", srcs: ["com/android/internal/display/BrightnessSynchronizer.java"], visibility: ["//frameworks/base/services/tests/mockingservicestests"], @@ -685,16 +697,31 @@ gen_readonly_feature_apis = select(release_flag("RELEASE_USE_SYSTEM_FEATURE_BUIL // Generates com.android.internal.pm.RoSystemFeatures, optionally compiling in // details about fixed system features defined by build flags. When disabled, // the APIs are simply passthrough stubs with no meaningful side effects. +// TODO(b/203143243): Implement the `--feature=` aggregation directly with a native soong module. genrule { name: "systemfeatures-gen-srcs", cmd: "$(location systemfeatures-gen-tool) com.android.internal.pm.RoSystemFeatures " + // --readonly=false (default) makes the codegen an effective no-op passthrough API. " --readonly=" + gen_readonly_feature_apis + - // For now, only export "android.hardware.type.*" system features APIs. - // TODO(b/203143243): Use an intermediate soong var that aggregates all declared - // RELEASE_SYSTEM_FEATURE_* declarations into a single arg. - " --feature-apis=AUTOMOTIVE,WATCH,TELEVISION,EMBEDDED,PC" + - " > $(out)", + " --feature=AUTOMOTIVE:" + select(release_flag("RELEASE_SYSTEM_FEATURE_AUTOMOTIVE"), { + any @ value: value, + default: "", + }) + " --feature=EMBEDDED:" + select(release_flag("RELEASE_SYSTEM_FEATURE_EMBEDDED"), { + any @ value: value, + default: "", + }) + " --feature=LEANBACK:" + select(release_flag("RELEASE_SYSTEM_FEATURE_LEANBACK"), { + any @ value: value, + default: "", + }) + " --feature=PC:" + select(release_flag("RELEASE_SYSTEM_FEATURE_PC"), { + any @ value: value, + default: "", + }) + " --feature=TELEVISION:" + select(release_flag("RELEASE_SYSTEM_FEATURE_TELEVISION"), { + any @ value: value, + default: "", + }) + " --feature=WATCH:" + select(release_flag("RELEASE_SYSTEM_FEATURE_WATCH"), { + any @ value: value, + default: "", + }) + " > $(out)", out: [ "RoSystemFeatures.java", ], diff --git a/core/java/android/adaptiveauth/OWNERS b/core/java/android/adaptiveauth/OWNERS index 0218a7835586..bc8efa92c16f 100644 --- a/core/java/android/adaptiveauth/OWNERS +++ b/core/java/android/adaptiveauth/OWNERS @@ -1 +1 @@ -include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file +include /services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
\ No newline at end of file diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 7a1c759a3ec4..3fccc17e1bf1 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -30,6 +30,7 @@ import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext; import static java.lang.Character.MIN_VALUE; +import android.Manifest; import android.annotation.AnimRes; import android.annotation.CallSuper; import android.annotation.CallbackExecutor; @@ -3193,6 +3194,16 @@ public class Activity extends ContextThemeWrapper return ActivityTaskManager.getMaxNumPictureInPictureActions(this); } + private boolean isImplicitEnterPipProhibited() { + PackageManager pm = getPackageManager(); + if (android.app.Flags.enableTvImplicitEnterPipRestriction()) { + return pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK) + && pm.checkPermission(Manifest.permission.TV_IMPLICIT_ENTER_PIP, + getPackageName()) == PackageManager.PERMISSION_DENIED; + } + return false; + } + /** * @return Whether this device supports picture-in-picture. */ @@ -9192,6 +9203,8 @@ public class Activity extends ContextThemeWrapper } dispatchActivityPreResumed(); + mCanEnterPictureInPicture = true; + mFragments.execPendingActions(); mLastNonConfigurationInstances = null; @@ -9243,6 +9256,11 @@ public class Activity extends ContextThemeWrapper Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performPause:" + mComponent.getClassName()); } + + if (isImplicitEnterPipProhibited()) { + mCanEnterPictureInPicture = false; + } + dispatchActivityPrePaused(); mDoReportFullyDrawn = false; mFragments.dispatchPause(); @@ -9265,6 +9283,10 @@ public class Activity extends ContextThemeWrapper final void performUserLeaving() { onUserInteraction(); + + if (isImplicitEnterPipProhibited()) { + mCanEnterPictureInPicture = false; + } onUserLeaveHint(); } diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java index 68794588afc5..009cd7249dcd 100644 --- a/core/java/android/app/AppCompatTaskInfo.java +++ b/core/java/android/app/AppCompatTaskInfo.java @@ -101,7 +101,6 @@ public class AppCompatTaskInfo implements Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = { FLAG_UNDEFINED, - FLAG_BASE, FLAG_LETTERBOX_EDU_ENABLED, FLAG_ELIGIBLE_FOR_LETTERBOX_EDU, FLAG_LETTERBOXED, @@ -115,6 +114,10 @@ public class AppCompatTaskInfo implements Parcelable { }) public @interface TopActivityFlag {} + /** + * A combination of {@link TopActivityFlag}s that have been enabled through + * {@link #setTopActivityFlag}. + */ @TopActivityFlag private int mTopActivityFlags; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 38c8583dd024..fd70f4fc3f25 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1621,9 +1621,12 @@ public class AppOpsManager { */ public static final int OP_RANGING = AppOpEnums.APP_OP_RANGING; + /** @hide Access to read oxygen saturation. */ + public static final int OP_READ_OXYGEN_SATURATION = AppOpEnums.APP_OP_READ_OXYGEN_SATURATION; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 152; + public static final int _NUM_OP = 153; /** * All app ops represented as strings. @@ -1779,6 +1782,7 @@ public class AppOpsManager { OPSTR_READ_HEART_RATE, OPSTR_READ_SKIN_TEMPERATURE, OPSTR_RANGING, + OPSTR_READ_OXYGEN_SATURATION, }) public @interface AppOpString {} @@ -2521,6 +2525,11 @@ public class AppOpsManager { @FlaggedApi(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) public static final String OPSTR_READ_HEART_RATE = "android:read_heart_rate"; + /** @hide Access to read oxygen saturation. */ + @SystemApi + @FlaggedApi(Flags.FLAG_PLATFORM_OXYGEN_SATURATION_ENABLED) + public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation"; + /** @hide Access to read skin temperature. */ @SystemApi @FlaggedApi(Flags.FLAG_PLATFORM_SKIN_TEMPERATURE_ENABLED) @@ -2608,6 +2617,7 @@ public class AppOpsManager { // Health Flags.replaceBodySensorPermissionEnabled() ? OP_READ_HEART_RATE : OP_NONE, Flags.platformSkinTemperatureEnabled() ? OP_READ_SKIN_TEMPERATURE : OP_NONE, + Flags.platformOxygenSaturationEnabled() ? OP_READ_OXYGEN_SATURATION : OP_NONE, }; /** @@ -3129,6 +3139,11 @@ public class AppOpsManager { .setPermission(Flags.rangingPermissionEnabled()? Manifest.permission.RANGING : null) .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_READ_OXYGEN_SATURATION, OPSTR_READ_OXYGEN_SATURATION, + "READ_OXYGEN_SATURATION").setPermission( + Flags.platformOxygenSaturationEnabled() + ? HealthPermissions.READ_OXYGEN_SATURATION : null) + .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), }; // The number of longs needed to form a full bitmask of app ops diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 2cf718ef364f..3ae60d71facd 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1167,6 +1167,7 @@ class ContextImpl extends Context { @Override public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) { try { + intent.collectExtraIntentKeys(); ActivityTaskManager.getService().startActivityAsUser( mMainThread.getApplicationThread(), getOpPackageName(), getAttributionTag(), intent, intent.resolveTypeIfNeeded(getContentResolver()), diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index c6c0395d1b93..0381ee0e25ac 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -155,7 +155,7 @@ public class Notification implements Parcelable FOREGROUND_SERVICE_IMMEDIATE, FOREGROUND_SERVICE_DEFERRED }) - public @interface ServiceNotificationPolicy {}; + public @interface ServiceNotificationPolicy {} /** * If the Notification associated with starting a foreground service has been @@ -1754,10 +1754,6 @@ public class Notification implements Parcelable private Icon mSmallIcon; @UnsupportedAppUsage private Icon mLargeIcon; - private Icon mAppIcon; - - /** Cache for whether the notification was posted by a headless system app. */ - private Boolean mBelongsToHeadlessSystemApp = null; @UnsupportedAppUsage private String mChannelId; @@ -3247,86 +3243,6 @@ public class Notification implements Parcelable } /** - * Whether this notification was posted by a headless system app. - * - * If we don't have enough information to figure this out, this will return false. Therefore, - * false negatives are possible, but false positives should not be. - * - * @hide - */ - public boolean belongsToHeadlessSystemApp(Context context) { - Trace.beginSection("Notification#belongsToHeadlessSystemApp"); - - try { - if (mBelongsToHeadlessSystemApp != null) { - return mBelongsToHeadlessSystemApp; - } - - if (context == null) { - // Without a valid context, we don't know exactly. Let's assume it doesn't belong to - // a system app, but not cache the value. - return false; - } - - ApplicationInfo info = getApplicationInfo(context); - if (info != null) { - if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { - // It's not a system app at all. - mBelongsToHeadlessSystemApp = false; - } else { - // If there's no launch intent, it's probably a headless app. - final PackageManager pm = context.getPackageManager(); - mBelongsToHeadlessSystemApp = pm.getLaunchIntentForPackage(info.packageName) - == null; - } - } else { - // If for some reason we don't have the app info, we don't know; best assume it's - // not a system app. - return false; - } - return mBelongsToHeadlessSystemApp; - } finally { - Trace.endSection(); - } - } - - /** - * Get the resource ID of the app icon from application info. - * @hide - */ - public int getHeaderAppIconRes(Context context) { - ApplicationInfo info = getApplicationInfo(context); - if (info != null) { - return info.icon; - } - return 0; - } - - /** - * Load the app icon drawable from the package manager. This could result in a binder call. - * @hide - */ - public Drawable loadHeaderAppIcon(Context context) { - Trace.beginSection("Notification#loadHeaderAppIcon"); - - try { - if (context == null) { - Log.e(TAG, "Cannot load the app icon drawable with a null context"); - return null; - } - final PackageManager pm = context.getPackageManager(); - ApplicationInfo info = getApplicationInfo(context); - if (info == null) { - Log.e(TAG, "Cannot load the app icon drawable: no application info"); - return null; - } - return pm.getApplicationIcon(info); - } finally { - Trace.endSection(); - } - } - - /** * Fetch the application info from the notification, or the context if that isn't available. */ private ApplicationInfo getApplicationInfo(Context context) { @@ -4361,55 +4277,6 @@ public class Notification implements Parcelable } /** - * The colored app icon that can replace the small icon in the notification starting in V. - * - * Before using this value, you should first check whether it's actually being used by the - * notification by calling {@link Notification#shouldUseAppIcon()}. - * - * @hide - */ - public Icon getAppIcon() { - if (mAppIcon != null) { - return mAppIcon; - } - // If the app icon hasn't been loaded yet, check if we can load it without a context. - if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) { - final ApplicationInfo info = extras.getParcelable( - EXTRA_BUILDER_APPLICATION_INFO, - ApplicationInfo.class); - if (info != null) { - int appIconRes = info.icon; - if (appIconRes == 0) { - Log.w(TAG, "Failed to get the app icon: no icon in application info"); - return null; - } - mAppIcon = Icon.createWithResource(info.packageName, appIconRes); - return mAppIcon; - } else { - Log.e(TAG, "Failed to get the app icon: " - + "there's an EXTRA_BUILDER_APPLICATION_INFO in extras but it's null"); - } - } else { - Log.w(TAG, "Failed to get the app icon: no application info in extras"); - } - return null; - } - - /** - * Whether the notification is using the app icon instead of the small icon. - * @hide - */ - public boolean shouldUseAppIcon() { - if (Flags.notificationsUseAppIconInRow()) { - if (belongsToHeadlessSystemApp(/* context = */ null)) { - return false; - } - return getAppIcon() != null; - } - return false; - } - - /** * The large icon shown in this notification's content view. * @see Builder#getLargeIcon() * @see Builder#setLargeIcon(Icon) @@ -4566,19 +4433,6 @@ public class Notification implements Parcelable private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY = SystemProperties.getBoolean("notifications.only_title", true); - /** - * The lightness difference that has to be added to the primary text color to obtain the - * secondary text color when the background is light. - */ - private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20; - - /** - * The lightness difference that has to be added to the primary text color to obtain the - * secondary text color when the background is dark. - * A bit less then the above value, since it looks better on dark backgrounds. - */ - private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10; - private Context mContext; private Notification mN; private Bundle mUserExtras = new Bundle(); @@ -6451,36 +6305,12 @@ public class Notification implements Parcelable } private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) { - if (Flags.notificationsUseAppIcon()) { - // Override small icon with app icon - mN.mSmallIcon = Icon.createWithResource(mContext, - mN.getHeaderAppIconRes(mContext)); - } else if (mN.mSmallIcon == null && mN.icon != 0) { + if (mN.mSmallIcon == null && mN.icon != 0) { mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon); } - - boolean usingAppIcon = false; - if (Flags.notificationsUseAppIconInRow() && !mN.belongsToHeadlessSystemApp(mContext)) { - // Use the app icon in the view - int appIconRes = mN.getHeaderAppIconRes(mContext); - if (appIconRes != 0) { - mN.mAppIcon = Icon.createWithResource(mContext, appIconRes); - contentView.setImageViewIcon(R.id.icon, mN.mAppIcon); - contentView.setBoolean(R.id.icon, "setShouldShowAppIcon", true); - usingAppIcon = true; - } else { - Log.w(TAG, "bindSmallIcon: could not get the app icon"); - } - } - if (!usingAppIcon) { - contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); - } + contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel); - - // Don't change color if we're using the app icon. - if (!Flags.notificationsUseAppIcon() && !usingAppIcon) { - processSmallIconColor(mN.mSmallIcon, contentView, p); - } + processSmallIconColor(mN.mSmallIcon, contentView, p); } /** diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 04a9d13420ee..fee071b14016 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -359,3 +359,11 @@ flag { description: "Enables coexistence support for Setting MTE policy." bug: "376213673" } + +flag { + name: "enable_supervision_service_sync" + is_exported: true + namespace: "enterprise" + description: "Allows DPMS to enable or disable SupervisionService based on whether the device is being managed by the supervision role holder." + bug: "376213673" +} diff --git a/core/java/android/app/appfunctions/AppFunctionException.aidl b/core/java/android/app/appfunctions/AppFunctionException.aidl new file mode 100644 index 000000000000..7d432243b1b2 --- /dev/null +++ b/core/java/android/app/appfunctions/AppFunctionException.aidl @@ -0,0 +1,21 @@ +/* + * 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.appfunctions; + +import android.app.appfunctions.AppFunctionException; + +parcelable AppFunctionException;
\ No newline at end of file diff --git a/core/java/android/app/appfunctions/AppFunctionException.java b/core/java/android/app/appfunctions/AppFunctionException.java new file mode 100644 index 000000000000..d33b5055f9cc --- /dev/null +++ b/core/java/android/app/appfunctions/AppFunctionException.java @@ -0,0 +1,260 @@ +/* + * 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.appfunctions; + +import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** Represents an app function related errors. */ +@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) +public final class AppFunctionException extends Exception implements Parcelable { + /** + * The caller does not have the permission to execute an app function. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_DENIED = 1000; + + /** + * The caller supplied invalid arguments to the execution request. + * + * <p>This error may be considered similar to {@link IllegalArgumentException}. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_INVALID_ARGUMENT = 1001; + + /** + * The caller tried to execute a disabled app function. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_DISABLED = 1002; + + /** + * The caller tried to execute a function that does not exist. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_FUNCTION_NOT_FOUND = 1003; + + /** + * An internal unexpected error coming from the system. + * + * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_SYSTEM_ERROR = 2000; + + /** + * The operation was cancelled. Use this error code to report that a cancellation is done after + * receiving a cancellation signal. + * + * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_CANCELLED = 2001; + + /** + * 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 + * unexpected error is thrown from the bound application. + * + * <p>This error is in the {@link #ERROR_CATEGORY_APP} category. + */ + public static final int ERROR_APP_UNKNOWN_ERROR = 3000; + + /** + * The error category is unknown. + * + * <p>This is the default value for {@link #getErrorCategory}. + */ + public static final int ERROR_CATEGORY_UNKNOWN = 0; + + /** + * The error is caused by the app requesting a function execution. + * + * <p>For example, the caller provided invalid parameters in the execution request e.g. an + * invalid function ID. + * + * <p>Errors in the category fall in the range 1000-1999 inclusive. + */ + public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; + + /** + * The error is caused by an issue in the system. + * + * <p>For example, the AppFunctionService implementation is not found by the system. + * + * <p>Errors in the category fall in the range 2000-2999 inclusive. + */ + public static final int ERROR_CATEGORY_SYSTEM = 2; + + /** + * The error is caused by the app providing the function. + * + * <p>For example, the app crashed when the system is executing the request. + * + * <p>Errors in the category fall in the range 3000-3999 inclusive. + */ + public static final int ERROR_CATEGORY_APP = 3; + + private final int mErrorCode; + @Nullable private final String mErrorMessage; + @NonNull private final Bundle mExtras; + + /** + * @param errorCode The error code. + * @param errorMessage The error message. + */ + public AppFunctionException(@ErrorCode int errorCode, @Nullable String errorMessage) { + this(errorCode, errorMessage, Bundle.EMPTY); + } + + /** + * @param errorCode The error code. + * @param errorMessage The error message. + * @param extras The extras associated with this error. + */ + public AppFunctionException( + @ErrorCode int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) { + mErrorCode = errorCode; + mErrorMessage = errorMessage; + mExtras = Objects.requireNonNull(extras); + } + + private AppFunctionException(@NonNull Parcel in) { + mErrorCode = in.readInt(); + mErrorMessage = in.readString8(); + mExtras = Objects.requireNonNull(in.readBundle(getClass().getClassLoader())); + } + + /** Returns one of the {@code ERROR} constants. */ + @ErrorCode + public int getErrorCode() { + return mErrorCode; + } + + /** Returns the error message. */ + @Nullable + public String getErrorMessage() { + return mErrorMessage; + } + + /** + * Returns the error category. + * + * <p>This method categorizes errors based on their underlying cause, allowing developers to + * implement targeted error handling and provide more informative error messages to users. It + * maps ranges of error codes to specific error categories. + * + * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the error code does not belong to + * any error category. + * + * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding + * error code ranges. + */ + @ErrorCategory + public int getErrorCategory() { + if (mErrorCode >= 1000 && mErrorCode < 2000) { + return ERROR_CATEGORY_REQUEST_ERROR; + } + if (mErrorCode >= 2000 && mErrorCode < 3000) { + return ERROR_CATEGORY_SYSTEM; + } + if (mErrorCode >= 3000 && mErrorCode < 4000) { + return ERROR_CATEGORY_APP; + } + return ERROR_CATEGORY_UNKNOWN; + } + + /** Returns any extras associated with this error. */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mErrorCode); + dest.writeString8(mErrorMessage); + dest.writeBundle(mExtras); + } + + /** + * Error codes. + * + * @hide + */ + @IntDef( + prefix = {"ERROR_"}, + value = { + ERROR_DENIED, + ERROR_APP_UNKNOWN_ERROR, + ERROR_FUNCTION_NOT_FOUND, + ERROR_SYSTEM_ERROR, + ERROR_INVALID_ARGUMENT, + ERROR_DISABLED, + ERROR_CANCELLED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ErrorCode {} + + /** + * Error categories. + * + * @hide + */ + @IntDef( + prefix = {"ERROR_CATEGORY_"}, + value = { + ERROR_CATEGORY_UNKNOWN, + ERROR_CATEGORY_REQUEST_ERROR, + ERROR_CATEGORY_APP, + ERROR_CATEGORY_SYSTEM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ErrorCategory {} + + @NonNull + public static final Creator<AppFunctionException> CREATOR = + new Creator<>() { + @Override + public AppFunctionException createFromParcel(Parcel in) { + return new AppFunctionException(in); + } + + @Override + public AppFunctionException[] newArray(int size) { + return new AppFunctionException[size]; + } + }; +} diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java index 5ddb590add4c..ed088fed41c2 100644 --- a/core/java/android/app/appfunctions/AppFunctionManager.java +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -16,7 +16,7 @@ package android.app.appfunctions; -import static android.app.appfunctions.ExecuteAppFunctionResponse.getResultCode; +import static android.app.appfunctions.AppFunctionException.ERROR_SYSTEM_ERROR; import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER; import android.Manifest; @@ -39,7 +39,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.concurrent.Executor; -import java.util.function.Consumer; /** * Provides access to app functions. @@ -147,16 +146,16 @@ public final class AppFunctionManager { * @param request the request to execute the app function * @param executor the executor to run the callback * @param cancellationSignal the cancellation signal to cancel the execution. - * @param callback the callback to receive the function execution result. + * @param callback the callback to receive the function execution result or error. * <p>If the calling app does not own the app function or does not have {@code * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code * android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code - * ExecuteAppFunctionResponse.RESULT_DENIED}. + * AppFunctionException.ERROR_DENIED}. * <p>If the caller only has {@code android.permission.EXECUTE_APP_FUNCTIONS} but the * function requires {@code android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}, the execution - * result will contain {@code ExecuteAppFunctionResponse.RESULT_DENIED} + * result will contain {@code AppFunctionException.ERROR_DENIED} * <p>If the function requested for execution is disabled, then the execution result will - * contain {@code ExecuteAppFunctionResponse.RESULT_DISABLED} + * contain {@code AppFunctionException.ERROR_DISABLED} * <p>If the cancellation signal is issued, the operation is cancelled and no response is * returned to the caller. */ @@ -171,7 +170,9 @@ public final class AppFunctionManager { @NonNull ExecuteAppFunctionRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer<ExecuteAppFunctionResponse> callback) { + @NonNull + OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> + callback) { Objects.requireNonNull(request); Objects.requireNonNull(executor); Objects.requireNonNull(callback); @@ -186,20 +187,25 @@ public final class AppFunctionManager { aidlRequest, new IExecuteAppFunctionCallback.Stub() { @Override - public void onResult(ExecuteAppFunctionResponse result) { + public void onSuccess(ExecuteAppFunctionResponse result) { try { - executor.execute(() -> callback.accept(result)); + executor.execute(() -> callback.onResult(result)); } catch (RuntimeException e) { // Ideally shouldn't happen since errors are wrapped into - // the - // response, but we catch it here for additional safety. - callback.accept( - ExecuteAppFunctionResponse.newFailure( - getResultCode(e), - e.getMessage(), - /* extras= */ null)); + // the response, but we catch it here for additional safety. + executor.execute( + () -> + callback.onError( + new AppFunctionException( + ERROR_SYSTEM_ERROR, + e.getMessage()))); } } + + @Override + public void onError(AppFunctionException exception) { + executor.execute(() -> callback.onError(exception)); + } }); if (cancellationTransport != null) { cancellationSignal.setRemote(cancellationTransport); diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java index 06d95f5270c3..3ddda228d145 100644 --- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java +++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java @@ -213,9 +213,7 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { setEnabled(original.getEnabled()); } - /** - * Sets an indicator specifying the function enabled state. - */ + /** Sets an indicator specifying the function enabled state. */ @NonNull public Builder setEnabled(@EnabledState int enabledState) { if (enabledState != APP_FUNCTION_STATE_DEFAULT diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java index 63d187aa11ef..85b6ab2b4e61 100644 --- a/core/java/android/app/appfunctions/AppFunctionService.java +++ b/core/java/android/app/appfunctions/AppFunctionService.java @@ -17,7 +17,6 @@ package android.app.appfunctions; import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE; -import static android.app.appfunctions.ExecuteAppFunctionResponse.getResultCode; import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER; import static android.content.pm.PackageManager.PERMISSION_DENIED; @@ -32,10 +31,8 @@ import android.os.Binder; import android.os.CancellationSignal; import android.os.IBinder; import android.os.ICancellationSignal; +import android.os.OutcomeReceiver; import android.os.RemoteException; -import android.util.Log; - -import java.util.function.Consumer; /** * Abstract base class to provide app functions to the system. @@ -80,7 +77,9 @@ public abstract class AppFunctionService extends Service { @NonNull ExecuteAppFunctionRequest request, @NonNull String callingPackage, @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer<ExecuteAppFunctionResponse> callback); + @NonNull + OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> + callback); } /** @hide */ @@ -105,13 +104,22 @@ public abstract class AppFunctionService extends Service { request, callingPackage, buildCancellationSignal(cancellationCallback), - safeCallback::onResult); + new OutcomeReceiver<>() { + @Override + public void onResult(ExecuteAppFunctionResponse result) { + safeCallback.onResult(result); + } + + @Override + public void onError(AppFunctionException exception) { + safeCallback.onError(exception); + } + }); } catch (Exception ex) { // Apps should handle exceptions. But if they don't, report the error on // behalf of them. - safeCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - getResultCode(ex), ex.getMessage(), /* extras= */ null)); + safeCallback.onError( + new AppFunctionException(toErrorCode(ex), ex.getMessage())); } } }; @@ -164,12 +172,26 @@ public abstract class AppFunctionService extends Service { * @param request The function execution request. * @param callingPackage The package name of the app that is requesting the execution. * @param cancellationSignal A signal to cancel the execution. - * @param callback A callback to report back the result. + * @param callback A callback to report back the result or error. */ @MainThread public abstract void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull String callingPackage, @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer<ExecuteAppFunctionResponse> callback); + @NonNull + OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> + callback); + + /** + * Returns result codes from throwable. + * + * @hide + */ + private static @AppFunctionException.ErrorCode int toErrorCode(@NonNull Throwable t) { + if (t instanceof IllegalArgumentException) { + return AppFunctionException.ERROR_INVALID_ARGUMENT; + } + return AppFunctionException.ERROR_APP_UNKNOWN_ERROR; + } } diff --git a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java index a23f842e6eeb..1869d22ea080 100644 --- a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java +++ b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java @@ -38,7 +38,7 @@ public class AppFunctionStaticMetadataHelper { public static final String STATIC_SCHEMA_TYPE = "AppFunctionStaticMetadata"; public static final String STATIC_PROPERTY_ENABLED_BY_DEFAULT = "enabledByDefault"; public static final String STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS = - "restrictCallersWithExecuteAppFunctions"; + "restrictCallersWithExecuteAppFunctions"; public static final String APP_FUNCTION_STATIC_NAMESPACE = "app_functions"; public static final String PROPERTY_FUNCTION_ID = "functionId"; diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java index 41bb62270e9f..1557815a8468 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java @@ -111,8 +111,8 @@ public final class ExecuteAppFunctionRequest implements Parcelable { * Returns the function parameters. The key is the parameter name, and the value is the * parameter value. * - * <p>The bundle may have missing parameters. Developers are advised to implement defensive - * handling measures. + * <p>The {@link GenericDocument} may have missing parameters. Developers are advised to + * implement defensive handling measures. * * @see AppFunctionManager on how to determine the expected parameters. */ diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java index cdf02e6f5a09..f7030265b122 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java @@ -19,7 +19,6 @@ package android.app.appfunctions; import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER; import android.annotation.FlaggedApi; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appsearch.GenericDocument; @@ -27,8 +26,6 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** The response to an app function execution. */ @@ -45,10 +42,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable { Bundle extras = Objects.requireNonNull( parcel.readBundle(Bundle.class.getClassLoader())); - int resultCode = parcel.readInt(); - String errorMessage = parcel.readString8(); - return new ExecuteAppFunctionResponse( - resultWrapper, extras, resultCode, errorMessage); + return new ExecuteAppFunctionResponse(resultWrapper.getValue(), extras); } @Override @@ -71,113 +65,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable { * * <p>See {@link #getResultDocument} for more information on extracting the return value. */ - public static final String PROPERTY_RETURN_VALUE = "returnValue"; - - /** - * The call was successful. - * - * <p>This result code does not belong in an error category. - */ - public static final int RESULT_OK = 0; - - /** - * The caller does not have the permission to execute an app function. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_DENIED = 1000; - - /** - * The caller supplied invalid arguments to the execution request. - * - * <p>This error may be considered similar to {@link IllegalArgumentException}. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_INVALID_ARGUMENT = 1001; - - /** - * The caller tried to execute a disabled app function. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_DISABLED = 1002; - - /** - * The caller tried to execute a function that does not exist. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_FUNCTION_NOT_FOUND = 1003; - - /** - * An internal unexpected error coming from the system. - * - * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. - */ - public static final int RESULT_SYSTEM_ERROR = 2000; - - /** - * The operation was cancelled. Use this error code to report that a cancellation is done after - * receiving a cancellation signal. - * - * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. - */ - public static final int RESULT_CANCELLED = 2001; - - /** - * 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 - * unexpected error is thrown from the bound application. - * - * <p>This error is in the {@link #ERROR_CATEGORY_APP} category. - */ - public static final int RESULT_APP_UNKNOWN_ERROR = 3000; - - /** - * The error category is unknown. - * - * <p>This is the default value for {@link #getErrorCategory}. - */ - public static final int ERROR_CATEGORY_UNKNOWN = 0; - - /** - * The error is caused by the app requesting a function execution. - * - * <p>For example, the caller provided invalid parameters in the execution request e.g. an - * invalid function ID. - * - * <p>Errors in the category fall in the range 1000-1999 inclusive. - */ - public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; - - /** - * The error is caused by an issue in the system. - * - * <p>For example, the AppFunctionService implementation is not found by the system. - * - * <p>Errors in the category fall in the range 2000-2999 inclusive. - */ - public static final int ERROR_CATEGORY_SYSTEM = 2; - - /** - * The error is caused by the app providing the function. - * - * <p>For example, the app crashed when the system is executing the request. - * - * <p>Errors in the category fall in the range 3000-3999 inclusive. - */ - public static final int ERROR_CATEGORY_APP = 3; - - /** The result code of the app function execution. */ - @ResultCode private final int mResultCode; - - /** - * The error message associated with the result, if any. This is {@code null} if the result code - * is {@link #RESULT_OK}. - */ - @Nullable private final String mErrorMessage; + public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; /** * Returns the return value of the executed function. @@ -192,103 +80,21 @@ public final class ExecuteAppFunctionResponse implements Parcelable { /** Returns the additional metadata data relevant to this function execution response. */ @NonNull private final Bundle mExtras; - private ExecuteAppFunctionResponse( - @NonNull GenericDocumentWrapper resultDocumentWrapper, - @NonNull Bundle extras, - @ResultCode int resultCode, - @Nullable String errorMessage) { - mResultDocumentWrapper = Objects.requireNonNull(resultDocumentWrapper); - mExtras = Objects.requireNonNull(extras); - mResultCode = resultCode; - mErrorMessage = errorMessage; - } - /** - * Returns result codes from throwable. - * - * @hide - */ - @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) - static @ResultCode int getResultCode(@NonNull Throwable t) { - if (t instanceof IllegalArgumentException) { - return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT; - } - return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR; - } - - /** - * Returns a successful response. - * * @param resultDocument The return value of the executed function. - * @param extras The additional metadata for this function execution response. */ - @NonNull - @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) - public static ExecuteAppFunctionResponse newSuccess( - @NonNull GenericDocument resultDocument, @Nullable Bundle extras) { - Objects.requireNonNull(resultDocument); - Bundle actualExtras = getActualExtras(extras); - GenericDocumentWrapper resultDocumentWrapper = new GenericDocumentWrapper(resultDocument); - - return new ExecuteAppFunctionResponse( - resultDocumentWrapper, actualExtras, RESULT_OK, /* errorMessage= */ null); + public ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument) { + this(resultDocument, Bundle.EMPTY); } /** - * Returns a failure response. - * - * @param resultCode The result code of the app function execution. + * @param resultDocument The return value of the executed function. * @param extras The additional metadata for this function execution response. - * @param errorMessage The error message associated with the result, if any. - */ - @NonNull - @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) - public static ExecuteAppFunctionResponse newFailure( - @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) { - if (resultCode == RESULT_OK) { - throw new IllegalArgumentException("resultCode must not be RESULT_OK"); - } - Bundle actualExtras = getActualExtras(extras); - GenericDocumentWrapper emptyWrapper = - new GenericDocumentWrapper(new GenericDocument.Builder<>("", "", "").build()); - return new ExecuteAppFunctionResponse(emptyWrapper, actualExtras, resultCode, errorMessage); - } - - private static Bundle getActualExtras(@Nullable Bundle extras) { - if (extras == null) { - return Bundle.EMPTY; - } - return extras; - } - - /** - * Returns the error category of the {@link ExecuteAppFunctionResponse}. - * - * <p>This method categorizes errors based on their underlying cause, allowing developers to - * implement targeted error handling and provide more informative error messages to users. It - * maps ranges of result codes to specific error categories. - * - * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to - * ensure correct categorization of the failed response. - * - * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to - * any error category, for example, in the case of a successful result with {@link #RESULT_OK}. - * - * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding - * result code ranges. */ - @ErrorCategory - public int getErrorCategory() { - if (mResultCode >= 1000 && mResultCode < 2000) { - return ERROR_CATEGORY_REQUEST_ERROR; - } - if (mResultCode >= 2000 && mResultCode < 3000) { - return ERROR_CATEGORY_SYSTEM; - } - if (mResultCode >= 3000 && mResultCode < 4000) { - return ERROR_CATEGORY_APP; - } - return ERROR_CATEGORY_UNKNOWN; + public ExecuteAppFunctionResponse( + @NonNull GenericDocument resultDocument, @NonNull Bundle extras) { + mResultDocumentWrapper = new GenericDocumentWrapper(Objects.requireNonNull(resultDocument)); + mExtras = Objects.requireNonNull(extras); } /** @@ -296,9 +102,6 @@ public final class ExecuteAppFunctionResponse implements Parcelable { * * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. * - * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed - * function does not produce a return value. - * * <p>Sample code for extracting the return value: * * <pre> @@ -324,32 +127,6 @@ public final class ExecuteAppFunctionResponse implements Parcelable { return mExtras; } - /** - * Returns {@code true} if {@link #getResultCode} equals {@link - * ExecuteAppFunctionResponse#RESULT_OK}. - */ - public boolean isSuccess() { - return getResultCode() == RESULT_OK; - } - - /** - * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}. - */ - @ResultCode - public int getResultCode() { - return mResultCode; - } - - /** - * Returns the error message associated with this result. - * - * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. - */ - @Nullable - public String getErrorMessage() { - return mErrorMessage; - } - @Override public int describeContents() { return 0; @@ -359,43 +136,5 @@ public final class ExecuteAppFunctionResponse implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { mResultDocumentWrapper.writeToParcel(dest, flags); dest.writeBundle(mExtras); - dest.writeInt(mResultCode); - dest.writeString8(mErrorMessage); } - - /** - * Result codes. - * - * @hide - */ - @IntDef( - prefix = {"RESULT_"}, - value = { - RESULT_OK, - RESULT_DENIED, - RESULT_APP_UNKNOWN_ERROR, - RESULT_FUNCTION_NOT_FOUND, - RESULT_SYSTEM_ERROR, - RESULT_INVALID_ARGUMENT, - RESULT_DISABLED, - RESULT_CANCELLED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ResultCode {} - - /** - * Error categories. - * - * @hide - */ - @IntDef( - prefix = {"ERROR_CATEGORY_"}, - value = { - ERROR_CATEGORY_UNKNOWN, - ERROR_CATEGORY_REQUEST_ERROR, - ERROR_CATEGORY_APP, - ERROR_CATEGORY_SYSTEM - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ErrorCategory {} } diff --git a/core/java/android/app/appfunctions/GenericDocumentWrapper.java b/core/java/android/app/appfunctions/GenericDocumentWrapper.java index b29b64e44d21..541ca7458efe 100644 --- a/core/java/android/app/appfunctions/GenericDocumentWrapper.java +++ b/core/java/android/app/appfunctions/GenericDocumentWrapper.java @@ -34,9 +34,9 @@ import java.util.Objects; * <p>{#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder * directly or Android shared memory if the data is large. * - * <p>This class performs lazy unparcelling. The `GenericDocument` is only unparcelled - * from the underlying `Parcel` when {@link #getValue()} is called. This optimization - * allows the system server to pass through the generic document, without unparcel and parcel it. + * <p>This class performs lazy unparcelling. The `GenericDocument` is only unparcelled from the + * underlying `Parcel` when {@link #getValue()} is called. This optimization allows the system + * server to pass through the generic document, without unparcel and parcel it. * * @hide * @see Parcel#writeBlob(byte[]) @@ -45,8 +45,11 @@ public final class GenericDocumentWrapper implements Parcelable { @Nullable @GuardedBy("mLock") private GenericDocument mGenericDocument; + @GuardedBy("mLock") - @Nullable private Parcel mParcel; + @Nullable + private Parcel mParcel; + private final Object mLock = new Object(); public static final Creator<GenericDocumentWrapper> CREATOR = diff --git a/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl b/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl index 5323f9b627e3..69bbc0e5d275 100644 --- a/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl +++ b/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl @@ -17,8 +17,10 @@ package android.app.appfunctions; import android.app.appfunctions.ExecuteAppFunctionResponse; +import android.app.appfunctions.AppFunctionException; /** {@hide} */ oneway interface IExecuteAppFunctionCallback { - void onResult(in ExecuteAppFunctionResponse result); + void onSuccess(in ExecuteAppFunctionResponse result); + void onError(in AppFunctionException exception); } diff --git a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java index 00182447e9a8..2426daf5c9f2 100644 --- a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java +++ b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java @@ -17,17 +17,16 @@ package android.app.appfunctions; import android.annotation.NonNull; -import android.annotation.Nullable; import android.os.RemoteException; import android.util.Log; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; /** * A wrapper of IExecuteAppFunctionCallback which swallows the {@link RemoteException}. This - * callback is intended for one-time use only. Subsequent calls to onResult() will be ignored. + * callback is intended for one-time use only. Subsequent calls to onResult() or onError() will be + * ignored. * * @hide */ @@ -38,44 +37,41 @@ public class SafeOneTimeExecuteAppFunctionCallback { @NonNull private final IExecuteAppFunctionCallback mCallback; - @Nullable private final Consumer<ExecuteAppFunctionResponse> mOnDispatchCallback; - public SafeOneTimeExecuteAppFunctionCallback(@NonNull IExecuteAppFunctionCallback callback) { - this(callback, /* onDispatchCallback= */ null); - } - - /** - * @param callback The callback to wrap. - * @param onDispatchCallback An optional callback invoked after the wrapped callback has been - * dispatched with a result. This callback receives the result that has been dispatched. - */ - public SafeOneTimeExecuteAppFunctionCallback( - @NonNull IExecuteAppFunctionCallback callback, - @Nullable Consumer<ExecuteAppFunctionResponse> onDispatchCallback) { mCallback = Objects.requireNonNull(callback); - mOnDispatchCallback = onDispatchCallback; } /** Invoke wrapped callback with the result. */ public void onResult(@NonNull ExecuteAppFunctionResponse result) { if (!mOnResultCalled.compareAndSet(false, true)) { - Log.w(TAG, "Ignore subsequent calls to onResult()"); + Log.w(TAG, "Ignore subsequent calls to onResult/onError()"); return; } try { - mCallback.onResult(result); + mCallback.onSuccess(result); } catch (RemoteException ex) { // Failed to notify the other end. Ignore. Log.w(TAG, "Failed to invoke the callback", ex); } - if (mOnDispatchCallback != null) { - mOnDispatchCallback.accept(result); + } + + /** Invoke wrapped callback with the error. */ + public void onError(@NonNull AppFunctionException error) { + if (!mOnResultCalled.compareAndSet(false, true)) { + Log.w(TAG, "Ignore subsequent calls to onResult/onError()"); + return; + } + try { + mCallback.onError(error); + } catch (RemoteException ex) { + // Failed to notify the other end. Ignore. + Log.w(TAG, "Failed to invoke the callback", ex); } } /** - * Disables this callback. Subsequent calls to {@link #onResult(ExecuteAppFunctionResponse)} - * will be ignored. + * Disables this callback. Subsequent calls to {@link #onResult(ExecuteAppFunctionResponse)} or + * {@link #onError(AppFunctionException)} will be ignored. */ public void disable() { mOnResultCalled.set(true); diff --git a/core/java/android/app/assist/AssistContent.java b/core/java/android/app/assist/AssistContent.java index a48868906487..43a46ba7885d 100644 --- a/core/java/android/app/assist/AssistContent.java +++ b/core/java/android/app/assist/AssistContent.java @@ -1,5 +1,6 @@ package android.app.assist; +import android.annotation.FlaggedApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.Intent; @@ -15,6 +16,20 @@ import android.os.Parcelable; * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}. */ public class AssistContent implements Parcelable { + /** + * Extra for a {@link Bundle} that provides contextual AppFunction's information about the + * content currently being viewed in the application. + * <p> + * This extra can be optionally supplied in the {@link AssistContent#getExtras()} bundle. + * <p> + * The schema of the {@link Bundle} in this extra is defined in the AppFunction SDK. + * + * @see android.app.appfunctions.AppFunctionManager + */ + @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) + public static final String EXTRA_APP_FUNCTION_DATA = + "android.app.assist.extra.APP_FUNCTION_DATA"; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private boolean mIsAppProvidedIntent = false; private boolean mIsAppProvidedWebUri = false; diff --git a/core/java/android/app/multitasking.aconfig b/core/java/android/app/multitasking.aconfig index 9a645192a155..c8455c1f439f 100644 --- a/core/java/android/app/multitasking.aconfig +++ b/core/java/android/app/multitasking.aconfig @@ -8,3 +8,11 @@ flag { description: "Enables PiP UI state callback on entering" bug: "303718131" } + +flag { + name: "enable_tv_implicit_enter_pip_restriction" + is_exported: true + namespace: "tv_system_ui" + description: "Enables restrictions to PiP entry on TV for setAutoEnterEnabled and lifecycle methods" + bug: "283115999" +} diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 0fc4291d15ab..ee93870be055 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -8,8 +8,7 @@ container: "system" flag { name: "notifications_redesign_app_icons" namespace: "systemui" - description: "Notifications Redesign: Use app icons in notification rows (not to be confused with" - " notifications_use_app_icons, notifications_use_app_icon_in_row which are just experiments)." + description: "Notifications Redesign: Use app icons in notification rows" bug: "371174789" } @@ -110,31 +109,6 @@ flag { } } -# vvv Prototypes for using app icons in notifications vvv - -flag { - name: "notifications_use_app_icon" - namespace: "systemui" - description: "Experiment to replace the small icon in a notification with the app icon. This includes the status bar, AOD, shelf and notification row itself." - bug: "335211019" -} - -flag { - name: "notifications_use_app_icon_in_row" - namespace: "systemui" - description: "Experiment to replace the small icon in a notification row with the app icon." - bug: "335211019" -} - -flag { - name: "notifications_use_monochrome_app_icon" - namespace: "systemui" - description: "Experiment to replace the notification icon in the status bar and shelf with the monochrome app icon, if available." - bug: "335211019" -} - -# ^^^ Prototypes for using app icons in notifications ^^^ - flag { name: "notification_expansion_optional" namespace: "systemui" diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index 9af20164a66d..c47fe236faf0 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -153,3 +153,10 @@ flag { bug: "371173368" is_exported: true } + +flag { + name: "vdm_settings" + namespace: "virtual_devices" + description: "Show virtual devices in Settings" + bug: "338974320" +} diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index ff0bb25bbccc..cc57dc05d6b1 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -398,6 +398,7 @@ public class ClipData implements Parcelable { * Retrieve the raw Intent contained in this Item. */ public Intent getIntent() { + Intent.maybeMarkAsMissingCreatorToken(mIntent); return mIntent; } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 186f7b3e111c..6086f2455a31 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -6802,6 +6802,12 @@ public abstract class Context { public static final String MEDIA_QUALITY_SERVICE = "media_quality"; /** + * Service to perform operations needed for dynamic instrumentation. + * @hide + */ + public static final String DYNAMIC_INSTRUMENTATION_SERVICE = "dynamic_instrumentation"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 6fa5a9b82858..c054b79f617e 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -87,6 +87,7 @@ import android.util.AttributeSet; import android.util.Log; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; import com.android.modules.expresslog.Counter; @@ -108,6 +109,7 @@ import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.TimeZone; +import java.util.function.Consumer; /** * An intent is an abstract description of an operation to be performed. It @@ -892,6 +894,20 @@ public class Intent implements Parcelable, Cloneable { public static void maybeMarkAsMissingCreatorToken(Object object) { if (object instanceof Intent intent) { maybeMarkAsMissingCreatorTokenInternal(intent); + } else if (object instanceof Parcelable[] parcelables) { + for (Parcelable p : parcelables) { + if (p instanceof Intent intent) { + maybeMarkAsMissingCreatorTokenInternal(intent); + } + } + } else if (object instanceof ArrayList parcelables) { + int N = parcelables.size(); + for (int i = 0; i < N; i++) { + Object p = parcelables.get(i); + if (p instanceof Intent intent) { + maybeMarkAsMissingCreatorTokenInternal(intent); + } + } } } @@ -12204,7 +12220,70 @@ public class Intent implements Parcelable, Cloneable { // Stores a creator token for an intent embedded as an extra intent in a top level intent, private IBinder mCreatorToken; // Stores all extra keys whose values are intents for a top level intent. - private ArraySet<String> mExtraIntentKeys; + private ArraySet<NestedIntentKey> mNestedIntentKeys; + } + + /** + * @hide + */ + public static class NestedIntentKey { + /** @hide */ + @IntDef(flag = true, prefix = {"NESTED_INTENT_KEY_TYPE"}, value = { + NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, + NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY, + NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST, + NESTED_INTENT_KEY_TYPE_CLIP_DATA, + }) + @Retention(RetentionPolicy.SOURCE) + private @interface NestedIntentKeyType { + } + + /** + * This flag indicates the key is for an extra parcel in mExtras. + */ + private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL = 1 << 0; + + /** + * This flag indicates the key is for an extra parcel array in mExtras and the index is the + * index of that array. + */ + private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY = 1 << 1; + + /** + * This flag indicates the key is for an extra parcel list in mExtras and the index is the + * index of that list. + */ + private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST = 1 << 2; + + /** + * This flag indicates the key is for an extra parcel in mClipData.mItems. + */ + private static final int NESTED_INTENT_KEY_TYPE_CLIP_DATA = 1 << 3; + + // type can be a short or even byte. But then probably cannot use @IntDef?? Also not sure + // if it is necessary. + private final @NestedIntentKeyType int mType; + private final String mKey; + private final int mIndex; + + private NestedIntentKey(@NestedIntentKeyType int type, String key, int index) { + this.mType = type; + this.mKey = key; + this.mIndex = index; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NestedIntentKey that = (NestedIntentKey) o; + return mType == that.mType && mIndex == that.mIndex && Objects.equals(mKey, that.mKey); + } + + @Override + public int hashCode() { + return Objects.hash(mType, mKey, mIndex); + } } private @Nullable CreatorTokenInfo mCreatorTokenInfo; @@ -12227,8 +12306,9 @@ public class Intent implements Parcelable, Cloneable { } /** @hide */ - public Set<String> getExtraIntentKeys() { - return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mExtraIntentKeys; + @VisibleForTesting + public Set<NestedIntentKey> getExtraIntentKeys() { + return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mNestedIntentKeys; } /** @hide */ @@ -12246,45 +12326,168 @@ public class Intent implements Parcelable, Cloneable { * @hide */ public void collectExtraIntentKeys() { - if (!preventIntentRedirect()) return; + if (preventIntentRedirect()) { + collectNestedIntentKeysRecur(new ArraySet<>()); + } + } - if (mExtras != null && !mExtras.isEmpty()) { + private void collectNestedIntentKeysRecur(Set<Intent> visited) { + if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) { for (String key : mExtras.keySet()) { - if (mExtras.get(key) instanceof Intent) { - if (mCreatorTokenInfo == null) { - mCreatorTokenInfo = new CreatorTokenInfo(); - } - if (mCreatorTokenInfo.mExtraIntentKeys == null) { - mCreatorTokenInfo.mExtraIntentKeys = new ArraySet<>(); - } - mCreatorTokenInfo.mExtraIntentKeys.add(key); + Object value = mExtras.get(key); + + if (value instanceof Intent intent && !visited.contains(intent)) { + handleNestedIntent(intent, visited, new NestedIntentKey( + NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0)); + } else if (value instanceof Parcelable[] parcelables) { + handleParcelableArray(parcelables, key, visited); + } else if (value instanceof ArrayList<?> parcelables) { + handleParcelableList(parcelables, key, visited); + } + } + } + + if (mClipData != null) { + for (int i = 0; i < mClipData.getItemCount(); i++) { + Intent intent = mClipData.getItemAt(i).mIntent; + if (intent != null && !visited.contains(intent)) { + handleNestedIntent(intent, visited, new NestedIntentKey( + NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA, null, i)); } } } } + private void handleNestedIntent(Intent intent, Set<Intent> visited, NestedIntentKey key) { + visited.add(intent); + if (mCreatorTokenInfo == null) { + mCreatorTokenInfo = new CreatorTokenInfo(); + } + if (mCreatorTokenInfo.mNestedIntentKeys == null) { + mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(); + } + mCreatorTokenInfo.mNestedIntentKeys.add(key); + intent.collectNestedIntentKeysRecur(visited); + } + + private void handleParcelableArray(Parcelable[] parcelables, String key, Set<Intent> visited) { + for (int i = 0; i < parcelables.length; i++) { + if (parcelables[i] instanceof Intent intent && !visited.contains(intent)) { + handleNestedIntent(intent, visited, new NestedIntentKey( + NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY, key, i)); + } + } + } + + private void handleParcelableList(ArrayList<?> parcelables, String key, Set<Intent> visited) { + for (int i = 0; i < parcelables.size(); i++) { + if (parcelables.get(i) instanceof Intent intent && !visited.contains(intent)) { + handleNestedIntent(intent, visited, new NestedIntentKey( + NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST, key, i)); + } + } + } + + private static final Consumer<Intent> CHECK_CREATOR_TOKEN_ACTION = intent -> { + intent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT; + if (intent.mExtras != null) { + intent.mExtras.enableTokenVerification(); + } + }; + /** @hide */ public void checkCreatorToken() { - if (mExtras == null) return; - if (mCreatorTokenInfo != null && mCreatorTokenInfo.mExtraIntentKeys != null) { - for (String key : mCreatorTokenInfo.mExtraIntentKeys) { - try { - Intent extraIntent = mExtras.getParcelable(key, Intent.class); - if (extraIntent == null) { - Log.w(TAG, "The key {" + key - + "} does not correspond to an intent in the bundle."); - continue; - } - extraIntent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT; - } catch (Exception e) { - Log.e(TAG, "Failed to validate creator token. key: " + key + ".", e); + forEachNestedCreatorToken(CHECK_CREATOR_TOKEN_ACTION); + + if (mExtras != null) { + // mark the bundle as intent extras after calls to getParcelable. + // otherwise, the logic to mark missing token would run before + // mark trusted creator token present. + mExtras.enableTokenVerification(); + } + } + + /** @hide */ + public void forEachNestedCreatorToken(Consumer<? super Intent> action) { + if (mExtras == null && mClipData == null) return; + + if (mCreatorTokenInfo != null && mCreatorTokenInfo.mNestedIntentKeys != null) { + int N = mCreatorTokenInfo.mNestedIntentKeys.size(); + for (int i = 0; i < N; i++) { + NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i); + Intent extraIntent = extractIntentFromKey(key); + + if (extraIntent != null) { + action.accept(extraIntent); + extraIntent.forEachNestedCreatorToken(action); + } else { + Log.w(TAG, getLogMessageForKey(key)); } } } - // mark the bundle as intent extras after calls to getParcelable. - // otherwise, the logic to mark missing token would run before - // mark trusted creator token present. - mExtras.setIsIntentExtra(); + } + + private Intent extractIntentFromKey(NestedIntentKey key) { + switch (key.mType) { + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL: + return mExtras == null ? null : mExtras.getParcelable(key.mKey, Intent.class); + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY: + if (mExtras == null) return null; + Intent[] extraIntents = mExtras.getParcelableArray(key.mKey, Intent.class); + if (extraIntents != null && key.mIndex < extraIntents.length) { + return extraIntents[key.mIndex]; + } + break; + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST: + if (mExtras == null) return null; + ArrayList<Intent> extraIntentsList = mExtras.getParcelableArrayList(key.mKey, + Intent.class); + if (extraIntentsList != null && key.mIndex < extraIntentsList.size()) { + return extraIntentsList.get(key.mIndex); + } + break; + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA: + if (mClipData == null) return null; + if (key.mIndex < mClipData.getItemCount()) { + ClipData.Item item = mClipData.getItemAt(key.mIndex); + if (item != null) { + return item.mIntent; + } + } + break; + } + return null; + } + + private String getLogMessageForKey(NestedIntentKey key) { + switch (key.mType) { + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL: + return "The key {" + key + "} does not correspond to an intent in the bundle."; + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY: + if (mExtras.getParcelableArray(key.mKey, Intent.class) == null) { + return "The key {" + key + + "} does not correspond to a Parcelable[] in the bundle."; + } else { + return "Parcelable[" + key.mIndex + "] for key {" + key + "} is not an intent."; + } + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST: + if (mExtras.getParcelableArrayList(key.mKey, Intent.class) == null) { + return "The key {" + key + + "} does not correspond to an ArrayList<Parcelable> in the bundle."; + } else { + return "List.get(" + key.mIndex + ") for key {" + key + "} is not an intent."; + } + case NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA: + if (key.mIndex >= mClipData.getItemCount()) { + return "Index out of range for clipData items. index: " + key.mIndex + + ". item counts: " + mClipData.getItemCount(); + } else { + return "clipData items at index [" + key.mIndex + + "] is null or does not contain an intent."; + } + default: + return "Unknown key type: " + key.mType; + } } /** @@ -12357,7 +12560,19 @@ public class Intent implements Parcelable, Cloneable { } else { out.writeInt(1); out.writeStrongBinder(mCreatorTokenInfo.mCreatorToken); - out.writeArraySet(mCreatorTokenInfo.mExtraIntentKeys); + + if (mCreatorTokenInfo.mNestedIntentKeys != null) { + final int N = mCreatorTokenInfo.mNestedIntentKeys.size(); + out.writeInt(N); + for (int i = 0; i < N; i++) { + NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i); + out.writeInt(key.mType); + out.writeString8(key.mKey); + out.writeInt(key.mIndex); + } + } else { + out.writeInt(0); + } } } } @@ -12422,7 +12637,18 @@ public class Intent implements Parcelable, Cloneable { if (in.readInt() != 0) { mCreatorTokenInfo = new CreatorTokenInfo(); mCreatorTokenInfo.mCreatorToken = in.readStrongBinder(); - mCreatorTokenInfo.mExtraIntentKeys = (ArraySet<String>) in.readArraySet(null); + + N = in.readInt(); + if (N > 0) { + mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(N); + for (int i = 0; i < N; i++) { + int type = in.readInt(); + String key = in.readString8(); + int index = in.readInt(); + mCreatorTokenInfo.mNestedIntentKeys.append( + new NestedIntentKey(type, key, index)); + } + } } } } diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index fff980fa8585..c424229f479b 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -353,7 +353,7 @@ flag { flag { name: "cloud_compilation_pm" is_exported: true - namespace: "package_manager_service" + namespace: "art_mainline" description: "Feature flag to enable the Cloud Compilation support on the package manager side." bug: "377474232" is_fixed_read_only: true diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig index 26ecbd1982d5..f23c193e2da0 100644 --- a/core/java/android/content/res/flags.aconfig +++ b/core/java/android/content/res/flags.aconfig @@ -95,3 +95,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "layout_readwrite_flags" + is_exported: true + namespace: "resource_manager" + description: "Feature flag for allowing read/write flags in layout files" + bug: "377974898" + # This flag is used to control aapt2 behavior. + is_fixed_read_only: true +} diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index ef59e0af3a27..93ef5c365730 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -45,7 +45,7 @@ import dalvik.system.CloseGuard; * </p> */ @RavenwoodKeepWholeClass -@RavenwoodRedirectionClass("CursorWindow_host") +@RavenwoodRedirectionClass("CursorWindow_ravenwood") public class CursorWindow extends SQLiteClosable implements Parcelable { private static final String STATS_TAG = "CursorWindowStats"; diff --git a/ravenwood/runtime-helper-src/framework/android/database/CursorWindow_host.java b/core/java/android/database/CursorWindow_ravenwood.java index e21a9cd71a2d..990ec5e9d59e 100644 --- a/ravenwood/runtime-helper-src/framework/android/database/CursorWindow_host.java +++ b/core/java/android/database/CursorWindow_ravenwood.java @@ -17,6 +17,7 @@ package android.database; import android.database.sqlite.SQLiteException; import android.os.Parcel; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.util.Base64; import java.text.DecimalFormat; @@ -26,9 +27,10 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; -public class CursorWindow_host { +@RavenwoodKeepWholeClass +class CursorWindow_ravenwood { - private static final HashMap<Long, CursorWindow_host> sInstances = new HashMap<>(); + private static final HashMap<Long, CursorWindow_ravenwood> sInstances = new HashMap<>(); private static long sNextId = 1; private String mName; @@ -41,7 +43,7 @@ public class CursorWindow_host { private final List<Row> mRows = new ArrayList<>(); public static long nativeCreate(String name, int cursorWindowSize) { - CursorWindow_host instance = new CursorWindow_host(); + CursorWindow_ravenwood instance = new CursorWindow_ravenwood(); instance.mName = name; long instanceId = sNextId++; sInstances.put(instanceId, instance); @@ -66,7 +68,7 @@ public class CursorWindow_host { } public static boolean nativeAllocRow(long windowPtr) { - CursorWindow_host instance = sInstances.get(windowPtr); + CursorWindow_ravenwood instance = sInstances.get(windowPtr); Row row = new Row(); row.mFields = new String[instance.mColumnNum]; row.mTypes = new int[instance.mColumnNum]; @@ -76,7 +78,7 @@ public class CursorWindow_host { } private static boolean put(long windowPtr, String value, int type, int row, int column) { - CursorWindow_host instance = sInstances.get(windowPtr); + CursorWindow_ravenwood instance = sInstances.get(windowPtr); if (row >= instance.mRows.size() || column >= instance.mColumnNum) { return false; } @@ -87,7 +89,7 @@ public class CursorWindow_host { } public static int nativeGetType(long windowPtr, int row, int column) { - CursorWindow_host instance = sInstances.get(windowPtr); + CursorWindow_ravenwood instance = sInstances.get(windowPtr); if (row >= instance.mRows.size() || column >= instance.mColumnNum) { return Cursor.FIELD_TYPE_NULL; } @@ -101,7 +103,7 @@ public class CursorWindow_host { } public static String nativeGetString(long windowPtr, int row, int column) { - CursorWindow_host instance = sInstances.get(windowPtr); + CursorWindow_ravenwood instance = sInstances.get(windowPtr); if (row >= instance.mRows.size() || column >= instance.mColumnNum) { return null; } @@ -164,7 +166,7 @@ public class CursorWindow_host { } public static void nativeWriteToParcel(long windowPtr, Parcel parcel) { - CursorWindow_host window = sInstances.get(windowPtr); + CursorWindow_ravenwood window = sInstances.get(windowPtr); parcel.writeString(window.mName); parcel.writeInt(window.mColumnNum); parcel.writeInt(window.mRows.size()); @@ -176,7 +178,7 @@ public class CursorWindow_host { public static long nativeCreateFromParcel(Parcel parcel) { long windowPtr = nativeCreate(null, 0); - CursorWindow_host window = sInstances.get(windowPtr); + CursorWindow_ravenwood window = sInstances.get(windowPtr); window.mName = parcel.readString(); window.mColumnNum = parcel.readInt(); int rowCount = parcel.readInt(); diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 86bbd4a57a63..987e2ad768b0 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -4289,6 +4289,39 @@ public abstract class CameraMetadata<TKey> { */ public static final int SYNC_FRAME_NUMBER_UNKNOWN = -2; + // + // Enumeration values for CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR + // + + /** + * <p>The camera can't accurately assess the scene's lighting to determine if a Night Mode + * Camera Extension capture would improve the photo. This can happen when the current + * camera configuration doesn't support night mode indicator detection, such as when + * the auto exposure mode is ON_AUTO_FLASH, ON_ALWAYS_FLASH, ON_AUTO_FLASH_REDEYE, or + * ON_EXTERNAL_FLASH.</p> + * @see CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR + */ + @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR) + public static final int EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN = 0; + + /** + * <p>The camera has detected lighting conditions that are sufficiently bright. Night + * Mode Camera Extensions is available but may not be able to optimize the camera + * settings to take a higher quality photo.</p> + * @see CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR + */ + @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR) + public static final int EXTENSION_NIGHT_MODE_INDICATOR_OFF = 1; + + /** + * <p>The camera has detected low-light conditions. It is recommended to use Night Mode + * Camera Extension to optimize the camera settings to take a high-quality photo in + * the dark.</p> + * @see CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR + */ + @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR) + public static final int EXTENSION_NIGHT_MODE_INDICATOR_ON = 2; + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index ae72ca40fc5a..bf3a072ff097 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -6016,6 +6016,38 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { public static final Key<Integer> EXTENSION_STRENGTH = new Key<Integer>("android.extension.strength", int.class); + /** + * <p>Indicates when to activate Night Mode Camera Extension for high-quality + * still captures in low-light conditions.</p> + * <p>Provides awareness to the application when the current scene can benefit from using a + * Night Mode Camera Extension to take a high-quality photo.</p> + * <p>Support for this capture result can be queried via + * {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureResultKeys }.</p> + * <p>If the device supports this capability then it will also support + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_NIGHT NIGHT} + * and will be available in both + * {@link android.hardware.camera2.CameraCaptureSession sessions} and + * {@link android.hardware.camera2.CameraExtensionSession sessions}.</p> + * <p>The value will be {@code UNKNOWN} in the following auto exposure modes: ON_AUTO_FLASH, + * ON_ALWAYS_FLASH, ON_AUTO_FLASH_REDEYE, or ON_EXTERNAL_FLASH.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN UNKNOWN}</li> + * <li>{@link #EXTENSION_NIGHT_MODE_INDICATOR_OFF OFF}</li> + * <li>{@link #EXTENSION_NIGHT_MODE_INDICATOR_ON ON}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN + * @see #EXTENSION_NIGHT_MODE_INDICATOR_OFF + * @see #EXTENSION_NIGHT_MODE_INDICATOR_ON + */ + @PublicKey + @NonNull + @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR) + public static final Key<Integer> EXTENSION_NIGHT_MODE_INDICATOR = + new Key<Integer>("android.extension.nightModeIndicator", int.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index e22c263e893d..1cc085658bfa 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -437,7 +437,7 @@ public class CameraMetadataNative implements Parcelable { } @Override - public void writeToParcel(Parcel dest, int flags) { + public synchronized void writeToParcel(Parcel dest, int flags) { nativeWriteToParcel(dest, mMetadataPtr); } @@ -479,7 +479,7 @@ public class CameraMetadataNative implements Parcelable { return getBase(key); } - public void readFromParcel(Parcel in) { + public synchronized void readFromParcel(Parcel in) { nativeReadFromParcel(in, mMetadataPtr); updateNativeAllocation(); } @@ -592,28 +592,33 @@ public class CameraMetadataNative implements Parcelable { } private <T> T getBase(Key<T> key) { - int tag; - if (key.hasTag()) { - tag = key.getTag(); - } else { - tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.getName()); - key.cacheTag(tag); - } - byte[] values = readValues(tag); - if (values == null) { - // If the key returns null, use the fallback key if exists. - // This is to support old key names for the newly published keys. - if (key.mFallbackName == null) { - return null; + int tag, nativeType; + byte[] values = null; + synchronized (this) { + if (key.hasTag()) { + tag = key.getTag(); + } else { + tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.getName()); + key.cacheTag(tag); } - tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.mFallbackName); values = readValues(tag); if (values == null) { - return null; + // If the key returns null, use the fallback key if exists. + // This is to support old key names for the newly published keys. + if (key.mFallbackName == null) { + return null; + } + tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.mFallbackName); + values = readValues(tag); + if (values == null) { + return null; + } } - } - int nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag); + nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag); + } + // This block of code doesn't need to be synchronized since we aren't writing or reading + // from the metadata buffer for this instance of CameraMetadataNative. Marshaler<T> marshaler = getMarshalerForKey(key, nativeType); ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); return marshaler.unmarshal(buffer); @@ -1945,8 +1950,12 @@ public class CameraMetadataNative implements Parcelable { setBase(key.getNativeKey(), value); } - private <T> void setBase(Key<T> key, T value) { - int tag; + // The whole method needs to be synchronized since we're making + // multiple calls to the native layer. From one call to the other (within setBase) + // we expect the metadata's properties such as vendor id etc to + // stay the same and as a result the whole method should be synchronized for safety. + private synchronized <T> void setBase(Key<T> key, T value) { + int tag, nativeType; if (key.hasTag()) { tag = key.getTag(); } else { @@ -1959,7 +1968,7 @@ public class CameraMetadataNative implements Parcelable { return; } // else update the entry to a new value - int nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag); + nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag); Marshaler<T> marshaler = getMarshalerForKey(key, nativeType); int size = marshaler.calculateMarshalSize(value); @@ -2162,7 +2171,7 @@ public class CameraMetadataNative implements Parcelable { return true; } - private void updateNativeAllocation() { + private synchronized void updateNativeAllocation() { long currentBufferSize = nativeGetBufferSize(mMetadataPtr); if (currentBufferSize != mBufferSize) { @@ -2245,6 +2254,11 @@ public class CameraMetadataNative implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private long mMetadataPtr; // native std::shared_ptr<CameraMetadata>* + // FastNative doesn't work with synchronized methods and we can do synchronization + // wherever needed in the java layer (caller). At some places in java such as + // setBase() / getBase(), we do need to synchronize the whole method, so leaving + // synchronized out for these native methods. + @FastNative private static native long nativeAllocate(); @FastNative @@ -2254,28 +2268,41 @@ public class CameraMetadataNative implements Parcelable { @FastNative private static native void nativeUpdate(long dst, long src); - private static synchronized native void nativeWriteToParcel(Parcel dest, long ptr); - private static synchronized native void nativeReadFromParcel(Parcel source, long ptr); - private static synchronized native void nativeSwap(long ptr, long otherPtr) + @FastNative + private static native void nativeWriteToParcel(Parcel dest, long ptr); + @FastNative + private static native void nativeReadFromParcel(Parcel source, long ptr); + @FastNative + private static native void nativeSwap(long ptr, long otherPtr) throws NullPointerException; @FastNative private static native void nativeSetVendorId(long ptr, long vendorId); - private static synchronized native void nativeClose(long ptr); - private static synchronized native boolean nativeIsEmpty(long ptr); - private static synchronized native int nativeGetEntryCount(long ptr); - private static synchronized native long nativeGetBufferSize(long ptr); + @FastNative + private static native void nativeClose(long ptr); + @FastNative + private static native boolean nativeIsEmpty(long ptr); + @FastNative + private static native int nativeGetEntryCount(long ptr); + @FastNative + private static native long nativeGetBufferSize(long ptr); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private static synchronized native byte[] nativeReadValues(int tag, long ptr); - private static synchronized native void nativeWriteValues(int tag, byte[] src, long ptr); - private static synchronized native void nativeDump(long ptr) throws IOException; // dump to LOGD + @FastNative + private static native byte[] nativeReadValues(int tag, long ptr); + @FastNative + private static native void nativeWriteValues(int tag, byte[] src, long ptr); + @FastNative + private static native void nativeDump(long ptr) throws IOException; // dump to LOGD - private static synchronized native ArrayList nativeGetAllVendorKeys(long ptr, Class keyClass); + @FastNative + private static native ArrayList nativeGetAllVendorKeys(long ptr, Class keyClass); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private static synchronized native int nativeGetTagFromKeyLocal(long ptr, String keyName) + @FastNative + private static native int nativeGetTagFromKeyLocal(long ptr, String keyName) throws IllegalArgumentException; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private static synchronized native int nativeGetTypeFromTagLocal(long ptr, int tag) + @FastNative + private static native int nativeGetTypeFromTagLocal(long ptr, int tag) throws IllegalArgumentException; @FastNative private static native int nativeGetTagFromKey(String keyName, long vendorId) @@ -2293,7 +2320,7 @@ public class CameraMetadataNative implements Parcelable { * @throws NullPointerException if other was null * @hide */ - public void swap(CameraMetadataNative other) { + public synchronized void swap(CameraMetadataNative other) { nativeSwap(mMetadataPtr, other.mMetadataPtr); mCameraId = other.mCameraId; mHasMandatoryConcurrentStreams = other.mHasMandatoryConcurrentStreams; @@ -2308,14 +2335,14 @@ public class CameraMetadataNative implements Parcelable { * * @hide */ - public void setVendorId(long vendorId) { + public synchronized void setVendorId(long vendorId) { nativeSetVendorId(mMetadataPtr, vendorId); } /** * @hide */ - public int getEntryCount() { + public synchronized int getEntryCount() { return nativeGetEntryCount(mMetadataPtr); } @@ -2324,7 +2351,7 @@ public class CameraMetadataNative implements Parcelable { * * @hide */ - public boolean isEmpty() { + public synchronized boolean isEmpty() { return nativeIsEmpty(mMetadataPtr); } @@ -2343,7 +2370,7 @@ public class CameraMetadataNative implements Parcelable { * * @hide */ - public <K> ArrayList<K> getAllVendorKeys(Class<K> keyClass) { + public synchronized <K> ArrayList<K> getAllVendorKeys(Class<K> keyClass) { if (keyClass == null) { throw new NullPointerException(); } @@ -2398,7 +2425,7 @@ public class CameraMetadataNative implements Parcelable { * * @hide */ - public void writeValues(int tag, byte[] src) { + public synchronized void writeValues(int tag, byte[] src) { nativeWriteValues(tag, src, mMetadataPtr); } @@ -2413,7 +2440,7 @@ public class CameraMetadataNative implements Parcelable { * @return {@code null} if there were 0 entries for this tag, a byte[] otherwise. * @hide */ - public byte[] readValues(int tag) { + public synchronized byte[] readValues(int tag) { // TODO: Optimization. Native code returns a ByteBuffer instead. return nativeReadValues(tag, mMetadataPtr); } @@ -2426,7 +2453,7 @@ public class CameraMetadataNative implements Parcelable { * * @hide */ - public void dumpToLog() { + public synchronized void dumpToLog() { try { nativeDump(mMetadataPtr); } catch (IOException e) { diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java index 6a96a54d93ba..529ee91cbfdf 100644 --- a/core/java/android/hardware/display/BrightnessInfo.java +++ b/core/java/android/hardware/display/BrightnessInfo.java @@ -113,16 +113,24 @@ public final class BrightnessInfo implements Parcelable { */ public final int brightnessMaxReason; + /** + * Whether the current brightness value is overridden by the application window via + * {@link android.view.WindowManager.LayoutParams#screenBrightness}. + */ + public final boolean isBrightnessOverrideByWindow; + public BrightnessInfo(float brightness, float brightnessMinimum, float brightnessMaximum, @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint, @BrightnessMaxReason int brightnessMaxReason) { this(brightness, brightness, brightnessMinimum, brightnessMaximum, highBrightnessMode, - highBrightnessTransitionPoint, brightnessMaxReason); + highBrightnessTransitionPoint, brightnessMaxReason, + false /* isBrightnessOverrideByWindow */); } public BrightnessInfo(float brightness, float adjustedBrightness, float brightnessMinimum, float brightnessMaximum, @HighBrightnessMode int highBrightnessMode, - float highBrightnessTransitionPoint, @BrightnessMaxReason int brightnessMaxReason) { + float highBrightnessTransitionPoint, @BrightnessMaxReason int brightnessMaxReason, + boolean isBrightnessOverrideByWindow) { this.brightness = brightness; this.adjustedBrightness = adjustedBrightness; this.brightnessMinimum = brightnessMinimum; @@ -130,6 +138,7 @@ public final class BrightnessInfo implements Parcelable { this.highBrightnessMode = highBrightnessMode; this.highBrightnessTransitionPoint = highBrightnessTransitionPoint; this.brightnessMaxReason = brightnessMaxReason; + this.isBrightnessOverrideByWindow = isBrightnessOverrideByWindow; } /** @@ -178,6 +187,7 @@ public final class BrightnessInfo implements Parcelable { dest.writeInt(highBrightnessMode); dest.writeFloat(highBrightnessTransitionPoint); dest.writeInt(brightnessMaxReason); + dest.writeBoolean(isBrightnessOverrideByWindow); } public static final @android.annotation.NonNull Creator<BrightnessInfo> CREATOR = @@ -201,6 +211,7 @@ public final class BrightnessInfo implements Parcelable { highBrightnessMode = source.readInt(); highBrightnessTransitionPoint = source.readFloat(); brightnessMaxReason = source.readInt(); + isBrightnessOverrideByWindow = source.readBoolean(); } } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index a81bcbcce9c9..28da644dd837 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -16,6 +16,7 @@ package android.hardware.display; +import static android.Manifest.permission.MANAGE_DISPLAYS; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.HdrCapabilities.HdrType; import static android.view.Display.INVALID_DISPLAY; @@ -575,14 +576,22 @@ public final class DisplayManager { EVENT_FLAG_DISPLAY_ADDED, EVENT_FLAG_DISPLAY_CHANGED, EVENT_FLAG_DISPLAY_REMOVED, - EVENT_FLAG_DISPLAY_BRIGHTNESS, - EVENT_FLAG_HDR_SDR_RATIO_CHANGED, - EVENT_FLAG_DISPLAY_CONNECTION_CHANGED, }) @Retention(RetentionPolicy.SOURCE) public @interface EventFlag {} /** + * @hide + */ + @LongDef(flag = true, prefix = {"PRIVATE_EVENT_FLAG_"}, value = { + PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS, + PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED, + PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PrivateEventFlag {} + + /** * Event type for when a new display is added. * * @see #registerDisplayListener(DisplayListener, Handler, long) @@ -618,7 +627,7 @@ public final class DisplayManager { * * @hide */ - public static final long EVENT_FLAG_DISPLAY_BRIGHTNESS = 1L << 3; + public static final long PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS = 1L << 0; /** * Event flag to register for a display's hdr/sdr ratio changes. This notification is sent @@ -631,14 +640,16 @@ public final class DisplayManager { * * @hide */ - public static final long EVENT_FLAG_HDR_SDR_RATIO_CHANGED = 1L << 4; + public static final long PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED = 1L << 1; /** * Event flag to register for a display's connection changed. * + * @see #registerDisplayListener(DisplayListener, Handler, long) * @hide */ - public static final long EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 5; + public static final long PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 2; + /** @hide */ public DisplayManager(Context context) { @@ -774,20 +785,49 @@ public final class DisplayManager { * @param listener The listener to register. * @param handler The handler on which the listener should be invoked, or null * if the listener should be invoked on the calling thread's looper. - * @param eventFlagsMask A bitmask of the event types for which this listener is subscribed. + * @param eventFlags A bitmask of the event types for which this listener is subscribed. + * + * @see #EVENT_FLAG_DISPLAY_ADDED + * @see #EVENT_FLAG_DISPLAY_CHANGED + * @see #EVENT_FLAG_DISPLAY_REMOVED + * @see #registerDisplayListener(DisplayListener, Handler) + * @see #unregisterDisplayListener + * + * @hide + */ + public void registerDisplayListener(@NonNull DisplayListener listener, + @Nullable Handler handler, @EventFlag long eventFlags) { + mGlobal.registerDisplayListener(listener, handler, + mGlobal.mapFlagsToInternalEventFlag(eventFlags, 0), + ActivityThread.currentPackageName()); + } + + /** + * Registers a display listener to receive notifications about given display event types. + * + * @param listener The listener to register. + * @param handler The handler on which the listener should be invoked, or null + * if the listener should be invoked on the calling thread's looper. + * @param eventFlags A bitmask of the event types for which this listener is subscribed. + * @param privateEventFlags A bitmask of the private event types for which this listener + * is subscribed. * * @see #EVENT_FLAG_DISPLAY_ADDED * @see #EVENT_FLAG_DISPLAY_CHANGED * @see #EVENT_FLAG_DISPLAY_REMOVED - * @see #EVENT_FLAG_DISPLAY_BRIGHTNESS + * @see #PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS + * @see #PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED + * @see #PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED * @see #registerDisplayListener(DisplayListener, Handler) * @see #unregisterDisplayListener * * @hide */ public void registerDisplayListener(@NonNull DisplayListener listener, - @Nullable Handler handler, @EventFlag long eventFlagsMask) { - mGlobal.registerDisplayListener(listener, handler, eventFlagsMask, + @Nullable Handler handler, @EventFlag long eventFlags, + @PrivateEventFlag long privateEventFlags) { + mGlobal.registerDisplayListener(listener, handler, + mGlobal.mapFlagsToInternalEventFlag(eventFlags, privateEventFlags), ActivityThread.currentPackageName()); } @@ -1725,6 +1765,29 @@ public final class DisplayManager { } /** + * @return The current display topology that represents the relative positions of extended + * displays. + * + * @hide + */ + @RequiresPermission(MANAGE_DISPLAYS) + @Nullable + public DisplayTopology getDisplayTopology() { + return mGlobal.getDisplayTopology(); + } + + /** + * Set the relative positions between extended displays (display topology). + * @param topology The display topology to be set + * + * @hide + */ + @RequiresPermission(MANAGE_DISPLAYS) + public void setDisplayTopology(DisplayTopology topology) { + mGlobal.setDisplayTopology(topology); + } + + /** * Listens for changes in available display devices. */ public interface DisplayListener { diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 56307ae53a0c..03b44f63e3b7 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -18,12 +18,14 @@ package android.hardware.display; import static android.hardware.display.DisplayManager.EventFlag; +import static android.Manifest.permission.MANAGE_DISPLAYS; import static android.view.Display.HdrCapabilities.HdrType; import android.Manifest; import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IntDef; +import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -118,6 +120,24 @@ public final class DisplayManagerGlobal { public static final int EVENT_DISPLAY_CONNECTED = 6; public static final int EVENT_DISPLAY_DISCONNECTED = 7; + @LongDef(prefix = {"INTERNAL_EVENT_DISPLAY"}, flag = true, value = { + INTERNAL_EVENT_FLAG_DISPLAY_ADDED, + INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, + INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, + INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED, + INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED, + INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface InternalEventFlag {} + + public static final long INTERNAL_EVENT_FLAG_DISPLAY_ADDED = 1L << 0; + public static final long INTERNAL_EVENT_FLAG_DISPLAY_CHANGED = 1L << 1; + public static final long INTERNAL_EVENT_FLAG_DISPLAY_REMOVED = 1L << 2; + public static final long INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED = 1L << 3; + public static final long INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED = 1L << 4; + public static final long INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 5; + @UnsupportedAppUsage private static DisplayManagerGlobal sInstance; @@ -130,7 +150,7 @@ public final class DisplayManagerGlobal { private final IDisplayManager mDm; private DisplayManagerCallback mCallback; - private @EventFlag long mRegisteredEventFlagsMask = 0; + private @InternalEventFlag long mRegisteredInternalEventFlag = 0; private final CopyOnWriteArrayList<DisplayListenerDelegate> mDisplayListeners = new CopyOnWriteArrayList<>(); @@ -346,11 +366,11 @@ public final class DisplayManagerGlobal { * @param packageName of the calling package. */ public void registerDisplayListener(@NonNull DisplayListener listener, - @Nullable Handler handler, @EventFlag long eventFlagsMask, + @Nullable Handler handler, @InternalEventFlag long internalEventFlagsMask, String packageName) { Looper looper = getLooperForHandler(handler); Handler springBoard = new Handler(looper); - registerDisplayListener(listener, new HandlerExecutor(springBoard), eventFlagsMask, + registerDisplayListener(listener, new HandlerExecutor(springBoard), internalEventFlagsMask, packageName); } @@ -359,32 +379,34 @@ public final class DisplayManagerGlobal { * * @param listener The listener that will be called when display changes occur. * @param executor Executor for the thread that will be receiving the callbacks. Cannot be null. - * @param eventFlagsMask Flag of events to be listened to. + * @param internalEventFlagsMask Mask of events to be listened to. * @param packageName of the calling package. */ public void registerDisplayListener(@NonNull DisplayListener listener, - @NonNull Executor executor, @EventFlag long eventFlagsMask, String packageName) { + @NonNull Executor executor, @InternalEventFlag long internalEventFlagsMask, + String packageName) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } - if (eventFlagsMask == 0) { + if (internalEventFlagsMask == 0) { throw new IllegalArgumentException("The set of events to listen to must not be empty."); } if (extraLogging()) { Slog.i(TAG, "Registering Display Listener: " - + Long.toBinaryString(eventFlagsMask) + ", packageName: " + packageName); + + Long.toBinaryString(internalEventFlagsMask) + + ", packageName: " + packageName); } synchronized (mLock) { int index = findDisplayListenerLocked(listener); if (index < 0) { mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, - eventFlagsMask, packageName)); + internalEventFlagsMask, packageName)); registerCallbackIfNeededLocked(); } else { - mDisplayListeners.get(index).setEventFlagsMask(eventFlagsMask); + mDisplayListeners.get(index).setEventsMask(internalEventFlagsMask); } updateCallbackIfNeededLocked(); maybeLogAllDisplayListeners(); @@ -456,17 +478,17 @@ public final class DisplayManagerGlobal { return -1; } - @EventFlag - private int calculateEventFlagsMaskLocked() { - int mask = 0; + @InternalEventFlag + private long calculateEventsMaskLocked() { + long mask = 0; final int numListeners = mDisplayListeners.size(); for (int i = 0; i < numListeners; i++) { - mask |= mDisplayListeners.get(i).mEventFlagsMask; + mask |= mDisplayListeners.get(i).mInternalEventFlagsMask; } if (mDispatchNativeCallbacks) { - mask |= DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; + mask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED + | INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; } return mask; } @@ -479,14 +501,14 @@ public final class DisplayManagerGlobal { } private void updateCallbackIfNeededLocked() { - int mask = calculateEventFlagsMaskLocked(); + long mask = calculateEventsMaskLocked(); if (DEBUG) { - Log.d(TAG, "Flag for listener: " + mask); + Log.d(TAG, "Mask for listener: " + mask); } - if (mask != mRegisteredEventFlagsMask) { + if (mask != mRegisteredInternalEventFlag) { try { mDm.registerCallbackWithEventMask(mCallback, mask); - mRegisteredEventFlagsMask = mask; + mRegisteredInternalEventFlag = mask; } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -1264,12 +1286,37 @@ public final class DisplayManagerGlobal { } } + /** + * @see DisplayManager#getDisplayTopology + */ + @RequiresPermission(MANAGE_DISPLAYS) + @Nullable + public DisplayTopology getDisplayTopology() { + try { + return mDm.getDisplayTopology(); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * @see DisplayManager#setDisplayTopology + */ + @RequiresPermission(MANAGE_DISPLAYS) + public void setDisplayTopology(DisplayTopology topology) { + try { + mDm.setDisplayTopology(topology); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { @Override public void onDisplayEvent(int displayId, @DisplayEvent int event) { if (DEBUG) { - Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + eventToString( - event)); + Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + + eventToString(event)); } handleDisplayEvent(displayId, event, false /* forceUpdate */); } @@ -1277,7 +1324,7 @@ public final class DisplayManagerGlobal { private static final class DisplayListenerDelegate { public final DisplayListener mListener; - public volatile long mEventFlagsMask; + public volatile long mInternalEventFlagsMask; private final DisplayInfo mDisplayInfo = new DisplayInfo(); private final Executor mExecutor; @@ -1285,10 +1332,10 @@ public final class DisplayManagerGlobal { private final String mPackageName; DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor, - @EventFlag long eventFlag, String packageName) { + @InternalEventFlag long internalEventFlag, String packageName) { mExecutor = executor; mListener = listener; - mEventFlagsMask = eventFlag; + mInternalEventFlagsMask = internalEventFlag; mPackageName = packageName; } @@ -1310,16 +1357,16 @@ public final class DisplayManagerGlobal { mGenerationId.incrementAndGet(); } - void setEventFlagsMask(@EventFlag long newEventsFlag) { - mEventFlagsMask = newEventsFlag; + void setEventsMask(@InternalEventFlag long newInternalEventFlagsMask) { + mInternalEventFlagsMask = newInternalEventFlagsMask; } - private void handleDisplayEventInner(int displayId, @DisplayEvent int eventFlagsMask, + private void handleDisplayEventInner(int displayId, @DisplayEvent int event, @Nullable DisplayInfo info, boolean forceUpdate) { if (extraLogging()) { - Slog.i(TAG, "DLD(" + eventToString(eventFlagsMask) + Slog.i(TAG, "DLD(" + eventToString(event) + ", display=" + displayId - + ", mEventsFlagMask=" + Long.toBinaryString(mEventFlagsMask) + + ", mEventsMask=" + Long.toBinaryString(mInternalEventFlagsMask) + ", mPackageName=" + mPackageName + ", displayInfo=" + info + ", listener=" + mListener.getClass() + ")"); @@ -1327,18 +1374,19 @@ public final class DisplayManagerGlobal { if (DEBUG) { Trace.beginSection( TextUtils.trimToSize( - "DLD(" + eventToString(eventFlagsMask) + "DLD(" + eventToString(event) + ", display=" + displayId + ", listener=" + mListener.getClass() + ")", 127)); } - switch (eventFlagsMask) { + switch (event) { case EVENT_DISPLAY_ADDED: - if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) { + if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_ADDED) != 0) { mListener.onDisplayAdded(displayId); } break; case EVENT_DISPLAY_CHANGED: - if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) { + if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_CHANGED) + != 0) { if (info != null && (forceUpdate || !info.equals(mDisplayInfo))) { if (extraLogging()) { Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: " @@ -1350,29 +1398,32 @@ public final class DisplayManagerGlobal { } break; case EVENT_DISPLAY_BRIGHTNESS_CHANGED: - if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0) { + if ((mInternalEventFlagsMask + & INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED) != 0) { mListener.onDisplayChanged(displayId); } break; case EVENT_DISPLAY_REMOVED: - if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0) { + if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_REMOVED) + != 0) { mListener.onDisplayRemoved(displayId); } break; case EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED: - if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0) { + if ((mInternalEventFlagsMask + & INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED) != 0) { mListener.onDisplayChanged(displayId); } break; case EVENT_DISPLAY_CONNECTED: - if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) - != 0) { + if ((mInternalEventFlagsMask + & INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) { mListener.onDisplayConnected(displayId); } break; case EVENT_DISPLAY_DISCONNECTED: - if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) - != 0) { + if ((mInternalEventFlagsMask + & INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) { mListener.onDisplayDisconnected(displayId); } break; @@ -1384,7 +1435,7 @@ public final class DisplayManagerGlobal { @Override public String toString() { - return "mEventFlagsMask: {" + mEventFlagsMask + "}, for " + mListener.getClass(); + return "flag: {" + mInternalEventFlagsMask + "}, for " + mListener.getClass(); } } @@ -1532,4 +1583,53 @@ public final class DisplayManagerGlobal { private static boolean extraLogging() { return sExtraDisplayListenerLogging; } + + + /** + * Maps the supplied public and private event flags to a unified InternalEventFlag + * @param eventFlags A bitmask of the event types for which this listener is subscribed. + * @param privateEventFlags A bitmask of the private event types for which this listener + * is subscribed. + * @return returns the bitmask of both public and private event flags unified to + * InternalEventFlag + */ + public @InternalEventFlag long mapFlagsToInternalEventFlag(@EventFlag long eventFlags, + @DisplayManager.PrivateEventFlag long privateEventFlags) { + return mapPrivateEventFlags(privateEventFlags) | mapPublicEventFlags(eventFlags); + } + + private long mapPrivateEventFlags(@DisplayManager.PrivateEventFlag long privateEventFlags) { + long baseEventMask = 0; + if ((privateEventFlags & DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0) { + baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED; + } + + if ((privateEventFlags & DisplayManager.PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0) { + baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED; + } + + if ((privateEventFlags + & DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) { + baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED; + } + return baseEventMask; + } + + private long mapPublicEventFlags(@EventFlag long eventFlags) { + long baseEventMask = 0; + if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) { + baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED; + } + + if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) { + baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_CHANGED; + } + + if ((eventFlags + & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0) { + baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; + } + + return baseEventMask; + } } diff --git a/core/java/android/hardware/display/DisplayTopology.aidl b/core/java/android/hardware/display/DisplayTopology.aidl new file mode 100644 index 000000000000..e69b777a30de --- /dev/null +++ b/core/java/android/hardware/display/DisplayTopology.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display; + +parcelable DisplayTopology; diff --git a/services/core/java/com/android/server/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java index fdadafeb98c9..e349b81614bc 100644 --- a/services/core/java/com/android/server/display/DisplayTopology.java +++ b/core/java/android/hardware/display/DisplayTopology.java @@ -14,25 +14,34 @@ * limitations under the License. */ -package com.android.server.display; +package android.hardware.display; -import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_BOTTOM; -import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_LEFT; -import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_TOP; -import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_RIGHT; +import static android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM; +import static android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT; +import static android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT; +import static android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP; +import android.annotation.IntDef; import android.annotation.Nullable; import android.graphics.RectF; +import android.os.Parcel; +import android.os.Parcelable; import android.util.IndentingPrintWriter; import android.util.Pair; import android.util.Slog; import android.view.Display; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -42,24 +51,59 @@ import java.util.Queue; /** * Represents the relative placement of extended displays. * Does not support concurrent calls, so a lock should be held when calling into this class. + * + * @hide */ -class DisplayTopology { +public final class DisplayTopology implements Parcelable { private static final String TAG = "DisplayTopology"; private static final float EPSILON = 0.0001f; + @android.annotation.NonNull + public static final Creator<DisplayTopology> CREATOR = + new Creator<>() { + @Override + public DisplayTopology createFromParcel(Parcel source) { + return new DisplayTopology(source); + } + + @Override + public DisplayTopology[] newArray(int size) { + return new DisplayTopology[size]; + } + }; + /** * The topology tree */ @Nullable - @VisibleForTesting - TreeNode mRoot; + private TreeNode mRoot; /** * The logical display ID of the primary display that will show certain UI elements. * This is not necessarily the same as the default display. */ + private int mPrimaryDisplayId = Display.INVALID_DISPLAY; + + public DisplayTopology() {} + @VisibleForTesting - int mPrimaryDisplayId = Display.INVALID_DISPLAY; + public DisplayTopology(TreeNode root, int primaryDisplayId) { + mRoot = root; + mPrimaryDisplayId = primaryDisplayId; + } + + public DisplayTopology(Parcel source) { + this(source.readTypedObject(TreeNode.CREATOR), source.readInt()); + } + + @Nullable + public TreeNode getRoot() { + return mRoot; + } + + public int getPrimaryDisplayId() { + return mPrimaryDisplayId; + } /** * Add a display to the topology. @@ -69,7 +113,7 @@ class DisplayTopology { * @param width The width of the display * @param height The height of the display */ - void addDisplay(int displayId, float width, float height) { + public void addDisplay(int displayId, float width, float height) { addDisplay(displayId, width, height, /* shouldLog= */ true); } @@ -79,7 +123,7 @@ class DisplayTopology { * one by one. * @param displayId The logical display ID */ - void removeDisplay(int displayId) { + public void removeDisplay(int displayId) { if (findDisplay(displayId, mRoot) == null) { return; } @@ -106,11 +150,22 @@ class DisplayTopology { } } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedObject(mRoot, flags); + dest.writeInt(mPrimaryDisplayId); + } + /** * Print the object's state and debug information into the given stream. * @param pw The stream to dump information to. */ - void dump(PrintWriter pw) { + public void dump(PrintWriter pw) { pw.println("DisplayTopology:"); pw.println("--------------------"); IndentingPrintWriter ipw = new IndentingPrintWriter(pw); @@ -126,13 +181,21 @@ class DisplayTopology { } } + @Override + public String toString() { + StringWriter out = new StringWriter(); + PrintWriter writer = new PrintWriter(out); + dump(writer); + return out.toString(); + } + private void addDisplay(int displayId, float width, float height, boolean shouldLog) { if (findDisplay(displayId, mRoot) != null) { throw new IllegalArgumentException( "DisplayTopology: attempting to add a display that already exists"); } if (mRoot == null) { - mRoot = new TreeNode(displayId, width, height, /* position= */ null, /* offset= */ 0); + mRoot = new TreeNode(displayId, width, height, /* position= */ 0, /* offset= */ 0); mPrimaryDisplayId = displayId; if (shouldLog) { Slog.i(TAG, "First display added: " + mRoot); @@ -241,7 +304,7 @@ class DisplayTopology { * Update the topology to remove any overlaps between displays. */ @VisibleForTesting - void normalize() { + public void normalize() { if (mRoot == null) { return; } @@ -341,6 +404,8 @@ class DisplayTopology { case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left); case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom); case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top); + default -> throw new IllegalStateException( + "Unexpected value: " + targetDisplay.mPosition); }; // Check that the offset is within bounds areTouching &= switch (targetDisplay.mPosition) { @@ -350,6 +415,8 @@ class DisplayTopology { case POSITION_TOP, POSITION_BOTTOM -> childBounds.right + EPSILON >= parentBounds.left && childBounds.left <= parentBounds.right + EPSILON; + default -> throw new IllegalStateException( + "Unexpected value: " + targetDisplay.mPosition); }; if (!areTouching) { @@ -379,36 +446,56 @@ class DisplayTopology { * @param b second float to compare * @return whether the two values are within a small enough tolerance value */ - public static boolean floatEquals(float a, float b) { - return a == b || Float.isNaN(a) && Float.isNaN(b) || Math.abs(a - b) < EPSILON; + private static boolean floatEquals(float a, float b) { + return a == b || (Float.isNaN(a) && Float.isNaN(b)) || Math.abs(a - b) < EPSILON; } - @VisibleForTesting - static class TreeNode { + public static final class TreeNode implements Parcelable { + public static final int POSITION_LEFT = 0; + public static final int POSITION_TOP = 1; + public static final int POSITION_RIGHT = 2; + public static final int POSITION_BOTTOM = 3; + + @IntDef(prefix = { "POSITION_" }, value = { + POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Position{} + + @android.annotation.NonNull + public static final Creator<TreeNode> CREATOR = + new Creator<>() { + @Override + public TreeNode createFromParcel(Parcel source) { + return new TreeNode(source); + } + + @Override + public TreeNode[] newArray(int size) { + return new TreeNode[size]; + } + }; /** * The logical display ID */ - @VisibleForTesting - final int mDisplayId; + private final int mDisplayId; /** * The width of the display in density-independent pixels (dp). */ - @VisibleForTesting - float mWidth; + private final float mWidth; /** * The height of the display in density-independent pixels (dp). */ - @VisibleForTesting - float mHeight; + private final float mHeight; /** * The position of this display relative to its parent. */ - @VisibleForTesting - Position mPosition; + @Position + private int mPosition; /** * The distance from the top edge of the parent display to the top edge of this display (in @@ -416,13 +503,13 @@ class DisplayTopology { * to the left edge of this display (in case of POSITION_TOP or POSITION_BOTTOM). The unit * used is density-independent pixels (dp). */ - @VisibleForTesting - float mOffset; + private float mOffset; - @VisibleForTesting - final List<TreeNode> mChildren = new ArrayList<>(); + private final List<TreeNode> mChildren = new ArrayList<>(); - TreeNode(int displayId, float width, float height, Position position, float offset) { + @VisibleForTesting + public TreeNode(int displayId, float width, float height, @Position int position, + float offset) { mDisplayId = displayId; mWidth = width; mHeight = height; @@ -430,11 +517,76 @@ class DisplayTopology { mOffset = offset; } + public TreeNode(Parcel source) { + this(source.readInt(), source.readFloat(), source.readFloat(), source.readInt(), + source.readFloat()); + source.readTypedList(mChildren, CREATOR); + } + + public int getDisplayId() { + return mDisplayId; + } + + public float getWidth() { + return mWidth; + } + + public float getHeight() { + return mHeight; + } + + public int getPosition() { + return mPosition; + } + + public float getOffset() { + return mOffset; + } + + public List<TreeNode> getChildren() { + return Collections.unmodifiableList(mChildren); + } + + @Override + public String toString() { + return "Display {id=" + mDisplayId + ", width=" + mWidth + ", height=" + mHeight + + ", position=" + positionToString(mPosition) + ", offset=" + mOffset + "}"; + } + + /** + * @param position The position + * @return The string representation + */ + public static String positionToString(@Position int position) { + return switch (position) { + case POSITION_LEFT -> "left"; + case POSITION_TOP -> "top"; + case POSITION_RIGHT -> "right"; + case POSITION_BOTTOM -> "bottom"; + default -> throw new IllegalStateException("Unexpected value: " + position); + }; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mDisplayId); + dest.writeFloat(mWidth); + dest.writeFloat(mHeight); + dest.writeInt(mPosition); + dest.writeFloat(mOffset); + dest.writeTypedList(mChildren); + } + /** * Print the object's state and debug information into the given stream. * @param ipw The stream to dump information to. */ - void dump(IndentingPrintWriter ipw) { + public void dump(IndentingPrintWriter ipw) { ipw.println(this); ipw.increaseIndent(); for (TreeNode child : mChildren) { @@ -443,15 +595,12 @@ class DisplayTopology { ipw.decreaseIndent(); } - @Override - public String toString() { - return "Display {id=" + mDisplayId + ", width=" + mWidth + ", height=" + mHeight - + ", position=" + mPosition + ", offset=" + mOffset + "}"; - } - + /** + * @param child The child to add + */ @VisibleForTesting - enum Position { - POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM + public void addChild(TreeNode child) { + mChildren.add(child); } } } diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index b612bca5671e..4fbdf7f5afc8 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -23,6 +23,7 @@ import android.hardware.display.BrightnessConfiguration; import android.hardware.display.BrightnessInfo; import android.hardware.display.Curve; import android.hardware.graphics.common.DisplayDecorationSupport; +import android.hardware.display.DisplayTopology; import android.hardware.display.HdrConversionMode; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; @@ -254,4 +255,13 @@ interface IDisplayManager { // Get the default doze brightness @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS") float getDefaultDozeBrightness(int displayId); + + // Get the display topology + @EnforcePermission("MANAGE_DISPLAYS") + @nullable + DisplayTopology getDisplayTopology(); + + // Set the display topology + @EnforcePermission("MANAGE_DISPLAYS") + void setDisplayTopology(in DisplayTopology topology); } diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 1b96224f03da..3284761eb273 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -276,9 +276,9 @@ interface IInputManager { @PermissionManuallyEnforced @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.MANAGE_KEY_GESTURES)") - void removeAllCustomInputGestures(int userId); + void removeAllCustomInputGestures(int userId, int tag); - AidlInputGestureData[] getCustomInputGestures(int userId); + AidlInputGestureData[] getCustomInputGestures(int userId, int tag); AidlInputGestureData[] getAppLaunchBookmarks(); } diff --git a/core/java/android/hardware/input/InputGestureData.java b/core/java/android/hardware/input/InputGestureData.java index ee0a2a9cf88c..f41550f6061e 100644 --- a/core/java/android/hardware/input/InputGestureData.java +++ b/core/java/android/hardware/input/InputGestureData.java @@ -296,4 +296,35 @@ public final class InputGestureData { public record Action(@KeyGestureEvent.KeyGestureType int keyGestureType, @Nullable AppLaunchData appLaunchData) { } + + /** Filter definition for InputGestureData */ + public enum Filter { + KEY(AidlInputGestureData.Trigger.Tag.key), + TOUCHPAD(AidlInputGestureData.Trigger.Tag.touchpadGesture); + + @AidlInputGestureData.Trigger.Tag + private final int mTag; + + Filter(@AidlInputGestureData.Trigger.Tag int tag) { + mTag = tag; + } + + @Nullable + public static Filter of(@AidlInputGestureData.Trigger.Tag int tag) { + return switch (tag) { + case AidlInputGestureData.Trigger.Tag.key -> KEY; + case AidlInputGestureData.Trigger.Tag.touchpadGesture -> TOUCHPAD; + default -> null; + }; + } + + @AidlInputGestureData.Trigger.Tag + public int getTag() { + return mTag; + } + + public boolean matches(@NonNull InputGestureData inputGestureData) { + return mTag == inputGestureData.mInputGestureData.trigger.getTag(); + } + } } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 9050ae235ce7..f8241925dff0 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1526,16 +1526,20 @@ public final class InputManager { /** Removes all custom input gestures * + * @param filter for removing all gestures of a category. If {@code null}, all custom input + * gestures will be removed + * * @hide */ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES) @UserHandleAware - public void removeAllCustomInputGestures() { + public void removeAllCustomInputGestures(@Nullable InputGestureData.Filter filter) { if (!enableCustomizableInputGestures()) { return; } try { - mIm.removeAllCustomInputGestures(mContext.getUserId()); + mIm.removeAllCustomInputGestures(mContext.getUserId(), + filter == null ? -1 : filter.getTag()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1543,16 +1547,20 @@ public final class InputManager { /** Get all custom input gestures * + * @param filter for fetching all gestures of a category. If {@code null}, then will return + * all custom input gestures + * * @hide */ @UserHandleAware - public List<InputGestureData> getCustomInputGestures() { + public List<InputGestureData> getCustomInputGestures(@Nullable InputGestureData.Filter filter) { List<InputGestureData> result = new ArrayList<>(); if (!enableCustomizableInputGestures()) { return result; } try { - for (AidlInputGestureData data : mIm.getCustomInputGestures(mContext.getUserId())) { + for (AidlInputGestureData data : mIm.getCustomInputGestures(mContext.getUserId(), + filter == null ? -1 : filter.getTag())) { result.add(new InputGestureData(data)); } } catch (RemoteException e) { diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index 38e32c61c99e..a8eb11d88aa4 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -30,14 +30,6 @@ flag { flag { namespace: "input_native" - name: "emoji_and_screenshot_keycodes_available" - is_exported: true - description: "Add new KeyEvent keycodes for opening Emoji Picker and Taking Screenshots" - bug: "315307777" -} - -flag { - namespace: "input_native" name: "keyboard_a11y_slow_keys_flag" description: "Controls if the slow keys accessibility feature for physical keyboard is available to the user" bug: "294546335" @@ -153,6 +145,13 @@ flag { } flag { + name: "enable_new_25q2_keycodes" + namespace: "input" + description: "Enables new 25Q2 keycodes" + bug: "365920375" +} + +flag { name: "override_power_key_behavior_in_focused_window" namespace: "input_native" description: "Allows privileged focused windows to capture power key events." diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java index 858ec23ebed8..af715e485b73 100644 --- a/core/java/android/hardware/location/ContextHubInfo.java +++ b/core/java/android/hardware/location/ContextHubInfo.java @@ -15,7 +15,6 @@ */ package android.hardware.location; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 6284e7061b88..494bfc926384 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -18,6 +18,7 @@ package android.hardware.location; import static java.util.Objects.requireNonNull; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -31,7 +32,6 @@ import android.app.ActivityThread; import android.app.PendingIntent; import android.chre.flags.Flags; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; import android.hardware.contexthub.ErrorCode; import android.os.Handler; @@ -484,15 +484,33 @@ public final class ContextHubManager { } } - /** - * Helper function to generate a stub for a query transaction callback. - * - * @param transaction the transaction to unblock when complete - * - * @return the callback - * - * @hide - */ + /** + * Returns the list of HubInfo objects describing the available hubs (including ContextHub and + * VendorHub). This method is primarily used for debugging purposes as most clients care about + * endpoints and services more than hubs. + * + * @return the list of HubInfo objects + * @see HubInfo + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @NonNull + @FlaggedApi(Flags.FLAG_OFFLOAD_API) + public List<HubInfo> getHubs() { + try { + return mService.getHubs(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Helper function to generate a stub for a query transaction callback. + * + * @param transaction the transaction to unblock when complete + * @return the callback + * @hide + */ private IContextHubTransactionCallback createQueryCallback( ContextHubTransaction<List<NanoAppState>> transaction) { return new IContextHubTransactionCallback.Stub() { diff --git a/core/java/android/hardware/location/HubInfo.aidl b/core/java/android/hardware/location/HubInfo.aidl new file mode 100644 index 000000000000..25b5b0aa1222 --- /dev/null +++ b/core/java/android/hardware/location/HubInfo.aidl @@ -0,0 +1,20 @@ +/* + * 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.hardware.location; + +/** @hide */ +parcelable HubInfo; diff --git a/core/java/android/hardware/location/HubInfo.java b/core/java/android/hardware/location/HubInfo.java new file mode 100644 index 000000000000..f7de1279672c --- /dev/null +++ b/core/java/android/hardware/location/HubInfo.java @@ -0,0 +1,153 @@ +/* + * 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.hardware.location; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.chre.flags.Flags; +import android.os.BadParcelableException; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Union type for {@link ContextHubInfo} and {@link VendorHubInfo} + * + * @hide + */ +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public final class HubInfo implements Parcelable { + + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {TYPE_CONTEXT_HUB, TYPE_VENDOR_HUB}) + private @interface HubType {} + + public static final int TYPE_CONTEXT_HUB = 0; + public static final int TYPE_VENDOR_HUB = 1; + + private final long mId; + @HubType private final int mType; + @Nullable private final ContextHubInfo mContextHubInfo; + @Nullable private final VendorHubInfo mVendorHubInfo; + + /** @hide */ + public HubInfo(long id, @NonNull ContextHubInfo contextHubInfo) { + mId = id; + mType = TYPE_CONTEXT_HUB; + mContextHubInfo = contextHubInfo; + mVendorHubInfo = null; + } + + /** @hide */ + public HubInfo(long id, @NonNull VendorHubInfo vendorHubInfo) { + mId = id; + mType = TYPE_VENDOR_HUB; + mContextHubInfo = null; + mVendorHubInfo = vendorHubInfo; + } + + private HubInfo(Parcel in) { + mId = in.readLong(); + mType = in.readInt(); + + switch (mType) { + case TYPE_CONTEXT_HUB: + mContextHubInfo = ContextHubInfo.CREATOR.createFromParcel(in); + mVendorHubInfo = null; + break; + case TYPE_VENDOR_HUB: + mVendorHubInfo = VendorHubInfo.CREATOR.createFromParcel(in); + mContextHubInfo = null; + break; + default: + throw new BadParcelableException("Parcelable has invalid type"); + } + } + + /** Get the hub unique identifier */ + public long getId() { + return mId; + } + + /** Get the hub type. The type can be {@link TYPE_CONTEXT_HUB} or {@link TYPE_VENDOR_HUB} */ + public int getType() { + return mType; + } + + /** Get the {@link ContextHubInfo} object, null if type is not {@link TYPE_CONTEXT_HUB} */ + @Nullable + public ContextHubInfo getContextHubInfo() { + return mContextHubInfo; + } + + /** Parcel implementation details */ + public int describeContents() { + if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) { + return mContextHubInfo.describeContents(); + } + if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) { + return mVendorHubInfo.describeContents(); + } + return 0; + } + + /** Parcel implementation details */ + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeLong(mId); + out.writeInt(mType); + + if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) { + mContextHubInfo.writeToParcel(out, flags); + } + + if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) { + mVendorHubInfo.writeToParcel(out, flags); + } + } + + @NonNull + @Override + public String toString() { + StringBuilder out = new StringBuilder(); + out.append("HubInfo ID: 0x"); + out.append(Long.toHexString(mId)); + out.append("\n"); + if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) { + out.append(" ContextHubDetails: "); + out.append(mContextHubInfo); + } + if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) { + out.append(" VendorHubDetails: "); + out.append(mVendorHubInfo); + } + return out.toString(); + } + + public static final @NonNull Creator<HubInfo> CREATOR = + new Creator<>() { + public HubInfo createFromParcel(Parcel in) { + return new HubInfo(in); + } + + public HubInfo[] newArray(int size) { + return new HubInfo[size]; + } + }; +} diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl index 11f3046150d3..b0cc763dc8fd 100644 --- a/core/java/android/hardware/location/IContextHubService.aidl +++ b/core/java/android/hardware/location/IContextHubService.aidl @@ -18,6 +18,7 @@ package android.hardware.location; // Declare any non-default types here with import statements import android.app.PendingIntent; +import android.hardware.location.HubInfo; import android.hardware.location.ContextHubInfo; import android.hardware.location.ContextHubMessage; import android.hardware.location.NanoApp; @@ -82,6 +83,10 @@ interface IContextHubService { @EnforcePermission("ACCESS_CONTEXT_HUB") List<ContextHubInfo> getContextHubs(); + // Returns a list of HubInfo objects of available hubs (including ContextHub and VendorHub) + @EnforcePermission("ACCESS_CONTEXT_HUB") + List<HubInfo> getHubs(); + // Loads a nanoapp at the specified hub (new API) @EnforcePermission("ACCESS_CONTEXT_HUB") void loadNanoAppOnHub( diff --git a/core/java/android/hardware/location/VendorHubInfo.aidl b/core/java/android/hardware/location/VendorHubInfo.aidl new file mode 100644 index 000000000000..a7936acbb654 --- /dev/null +++ b/core/java/android/hardware/location/VendorHubInfo.aidl @@ -0,0 +1,20 @@ +/* + * 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.location; + +/** @hide */ +parcelable VendorHubInfo;
\ No newline at end of file diff --git a/core/java/android/hardware/location/VendorHubInfo.java b/core/java/android/hardware/location/VendorHubInfo.java new file mode 100644 index 000000000000..26772b18176f --- /dev/null +++ b/core/java/android/hardware/location/VendorHubInfo.java @@ -0,0 +1,95 @@ +/* + * 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.hardware.location; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.chre.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelableHolder; + +/** + * Information about a VendorHub. VendorHub is similar to ContextHub, but it does not run the + * Context Hub Runtime Environment (or nano apps). It provides a unified endpoint messaging API + * through the ContextHub V4 HAL. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public final class VendorHubInfo implements Parcelable { + private final String mName; + private final int mVersion; + private final ParcelableHolder mExtendedInfo; + + /** @hide */ + public VendorHubInfo(android.hardware.contexthub.VendorHubInfo halHubInfo) { + mName = halHubInfo.name; + mVersion = halHubInfo.version; + mExtendedInfo = halHubInfo.extendedInfo; + } + + private VendorHubInfo(Parcel in) { + mName = in.readString(); + mVersion = in.readInt(); + mExtendedInfo = ParcelableHolder.CREATOR.createFromParcel(in); + } + + /** Get the hub name */ + @NonNull + public String getName() { + return mName; + } + + /** Get the hub version */ + public int getVersion() { + return mVersion; + } + + /** Parcel implementation details */ + public int describeContents() { + return mExtendedInfo.describeContents(); + } + + /** Parcel implementation details */ + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeString(mName); + out.writeInt(mVersion); + mExtendedInfo.writeToParcel(out, flags); + } + + @NonNull + @Override + public String toString() { + StringBuilder out = new StringBuilder(); + out.append("VendorHub Name : "); + out.append(mName); + out.append(", Version : "); + out.append(mVersion); + return out.toString(); + } + + public static final @NonNull Creator<VendorHubInfo> CREATOR = + new Creator<>() { + public VendorHubInfo createFromParcel(Parcel in) { + return new VendorHubInfo(in); + } + + public VendorHubInfo[] newArray(int size) { + return new VendorHubInfo[size]; + } + }; +} diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig index 1adefe5a0b86..1b2c575917b2 100644 --- a/core/java/android/net/vcn/flags.aconfig +++ b/core/java/android/net/vcn/flags.aconfig @@ -18,13 +18,6 @@ flag { } flag { - name: "safe_mode_timeout_config" - namespace: "vcn" - description: "Feature flag for adjustable safe mode timeout" - bug: "317406085" -} - -flag { name: "fix_config_garbage_collection" namespace: "vcn" description: "Handle race condition in subscription change" diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index c18fb0c38b82..99e7d166446e 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -281,7 +281,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** {@hide} */ - public void setIsIntentExtra() { + public void enableTokenVerification() { mFlags |= FLAG_VERIFY_TOKENS_PRESENT; } diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index acda0c5664fe..69bd6685bcac 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -55,7 +55,7 @@ import java.util.concurrent.locks.ReentrantLock; * {@link Looper#myQueue() Looper.myQueue()}. */ @RavenwoodKeepWholeClass -@RavenwoodRedirectionClass("MessageQueue_host") +@RavenwoodRedirectionClass("MessageQueue_ravenwood") public final class MessageQueue { private static final String TAG_L = "LegacyMessageQueue"; private static final String TAG_C = "ConcurrentMessageQueue"; diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java index 9db88d17614b..c2a47d767801 100644 --- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java +++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java @@ -54,7 +54,7 @@ import java.util.concurrent.locks.ReentrantLock; * {@link Looper#myQueue() Looper.myQueue()}. */ @RavenwoodKeepWholeClass -@RavenwoodRedirectionClass("MessageQueue_host") +@RavenwoodRedirectionClass("MessageQueue_ravenwood") public final class MessageQueue { private static final String TAG = "ConcurrentMessageQueue"; private static final boolean DEBUG = false; diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl index 6aa9852314df..ecb5e6f1b29a 100644 --- a/core/java/android/os/IVibratorManagerService.aidl +++ b/core/java/android/os/IVibratorManagerService.aidl @@ -17,13 +17,17 @@ package android.os; import android.os.CombinedVibration; +import android.os.ICancellationSignal; import android.os.IVibratorStateListener; import android.os.VibrationAttributes; import android.os.VibratorInfo; +import android.os.vibrator.IVibrationSession; +import android.os.vibrator.IVibrationSessionCallback; /** {@hide} */ interface IVibratorManagerService { int[] getVibratorIds(); + int getCapabilities(); VibratorInfo getVibratorInfo(int vibratorId); @EnforcePermission("ACCESS_VIBRATOR_STATE") boolean isVibrating(int vibratorId); @@ -50,4 +54,9 @@ interface IVibratorManagerService { oneway void performHapticFeedbackForInputDevice(int uid, int deviceId, String opPkg, int constant, int inputDeviceId, int inputSource, String reason, int flags, int privFlags); + + @EnforcePermission(allOf={"VIBRATE", "VIBRATE_VENDOR_EFFECTS", "START_VIBRATION_SESSIONS"}) + ICancellationSignal startVendorVibrationSession(int uid, int deviceId, String opPkg, + in int[] vibratorIds, in VibrationAttributes attributes, String reason, + in IVibrationSessionCallback callback); } diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java index 9f7b0b71ae95..cae82d010132 100644 --- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java +++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java @@ -45,7 +45,7 @@ import java.util.concurrent.atomic.AtomicLong; * {@link Looper#myQueue() Looper.myQueue()}. */ @RavenwoodKeepWholeClass -@RavenwoodRedirectionClass("MessageQueue_host") +@RavenwoodRedirectionClass("MessageQueue_ravenwood") public final class MessageQueue { private static final String TAG = "MessageQueue"; private static final boolean DEBUG = false; diff --git a/core/java/android/os/LockedMessageQueue/MessageQueue.java b/core/java/android/os/LockedMessageQueue/MessageQueue.java index f3eec13cb20f..2401f3d11bcf 100644 --- a/core/java/android/os/LockedMessageQueue/MessageQueue.java +++ b/core/java/android/os/LockedMessageQueue/MessageQueue.java @@ -48,7 +48,7 @@ import java.util.concurrent.atomic.AtomicLong; * {@link Looper#myQueue() Looper.myQueue()}. */ @RavenwoodKeepWholeClass -@RavenwoodRedirectionClass("MessageQueue_host") +@RavenwoodRedirectionClass("MessageQueue_ravenwood") public final class MessageQueue { private static final String TAG = "LockedMessageQueue"; private static final boolean DEBUG = false; diff --git a/ravenwood/runtime-helper-src/framework/android/os/MessageQueue_host.java b/core/java/android/os/MessageQueue_ravenwood.java index 1b63adc4319f..4033707c3253 100644 --- a/ravenwood/runtime-helper-src/framework/android/os/MessageQueue_host.java +++ b/core/java/android/os/MessageQueue_ravenwood.java @@ -16,13 +16,16 @@ package android.os; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; + import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; -public class MessageQueue_host { +@RavenwoodKeepWholeClass +class MessageQueue_ravenwood { private static final AtomicLong sNextId = new AtomicLong(1); - private static final Map<Long, MessageQueue_host> sInstances = new ConcurrentHashMap<>(); + private static final Map<Long, MessageQueue_ravenwood> sInstances = new ConcurrentHashMap<>(); private boolean mDeleted = false; @@ -37,8 +40,8 @@ public class MessageQueue_host { } } - private static MessageQueue_host getInstance(long id) { - MessageQueue_host q = sInstances.get(id); + private static MessageQueue_ravenwood getInstance(long id) { + MessageQueue_ravenwood q = sInstances.get(id); if (q == null) { throw new RuntimeException("MessageQueue doesn't exist with id=" + id); } @@ -48,7 +51,7 @@ public class MessageQueue_host { public static long nativeInit() { final long id = sNextId.getAndIncrement(); - final MessageQueue_host q = new MessageQueue_host(); + final MessageQueue_ravenwood q = new MessageQueue_ravenwood(); sInstances.put(id, q); return id; } diff --git a/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java index db323dcf9009..435c34f832c6 100644 --- a/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java +++ b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java @@ -52,7 +52,7 @@ import java.util.concurrent.atomic.AtomicLong; * {@link Looper#myQueue() Looper.myQueue()}. */ @RavenwoodKeepWholeClass -@RavenwoodRedirectionClass("MessageQueue_host") +@RavenwoodRedirectionClass("MessageQueue_ravenwood") public final class MessageQueue { private static final String TAG = "SemiConcurrentMessageQueue"; private static final boolean DEBUG = false; diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index 011a3ee91ada..c3cddf32f063 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -18,8 +18,11 @@ package android.os; import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.hardware.vibrator.IVibratorManager; +import android.os.vibrator.VendorVibrationSession; import android.os.vibrator.VibratorInfoFactory; import android.util.ArrayMap; import android.util.Log; @@ -53,6 +56,7 @@ public class SystemVibrator extends Vibrator { private final Object mLock = new Object(); @GuardedBy("mLock") private VibratorInfo mVibratorInfo; + private int[] mVibratorIds; @UnsupportedAppUsage public SystemVibrator(Context context) { @@ -71,7 +75,11 @@ public class SystemVibrator extends Vibrator { Log.w(TAG, "Failed to retrieve vibrator info; no vibrator manager."); return VibratorInfo.EMPTY_VIBRATOR_INFO; } - int[] vibratorIds = mVibratorManager.getVibratorIds(); + int[] vibratorIds = getVibratorIds(); + if (vibratorIds == null) { + Log.w(TAG, "Failed to retrieve vibrator info; error retrieving vibrator ids."); + return VibratorInfo.EMPTY_VIBRATOR_INFO; + } if (vibratorIds.length == 0) { // It is known that the device has no vibrator, so cache and return info that // reflects the lack of support for effects/primitives. @@ -95,20 +103,22 @@ public class SystemVibrator extends Vibrator { @Override public boolean hasVibrator() { - if (mVibratorManager == null) { + int[] vibratorIds = getVibratorIds(); + if (vibratorIds == null) { Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager."); return false; } - return mVibratorManager.getVibratorIds().length > 0; + return vibratorIds.length > 0; } @Override public boolean isVibrating() { - if (mVibratorManager == null) { + int[] vibratorIds = getVibratorIds(); + if (vibratorIds == null) { Log.w(TAG, "Failed to vibrate; no vibrator manager."); return false; } - for (int vibratorId : mVibratorManager.getVibratorIds()) { + for (int vibratorId : vibratorIds) { if (mVibratorManager.getVibrator(vibratorId).isVibrating()) { return true; } @@ -136,6 +146,11 @@ public class SystemVibrator extends Vibrator { Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager."); return; } + int[] vibratorIds = getVibratorIds(); + if (vibratorIds == null) { + Log.w(TAG, "Failed to add vibrate state listener; error retrieving vibrator ids."); + return; + } MultiVibratorStateListener delegate = null; try { synchronized (mRegisteredListeners) { @@ -145,7 +160,7 @@ public class SystemVibrator extends Vibrator { return; } delegate = new MultiVibratorStateListener(executor, listener); - delegate.register(mVibratorManager); + delegate.register(mVibratorManager, vibratorIds); mRegisteredListeners.put(listener, delegate); delegate = null; } @@ -184,6 +199,11 @@ public class SystemVibrator extends Vibrator { } @Override + public boolean areVendorSessionsSupported() { + return mVibratorManager.hasCapabilities(IVibratorManager.CAP_START_SESSIONS); + } + + @Override public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect, VibrationAttributes attrs) { if (mVibratorManager == null) { @@ -243,6 +263,41 @@ public class SystemVibrator extends Vibrator { mVibratorManager.cancel(usageFilter); } + @Override + public void startVendorSession(@NonNull VibrationAttributes attrs, @Nullable String reason, + @Nullable CancellationSignal cancellationSignal, @NonNull Executor executor, + @NonNull VendorVibrationSession.Callback callback) { + if (mVibratorManager == null) { + Log.w(TAG, "Failed to start vibration session; no vibrator manager."); + executor.execute( + () -> callback.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR)); + return; + } + int[] vibratorIds = getVibratorIds(); + if (vibratorIds == null) { + Log.w(TAG, "Failed to start vibration session; error retrieving vibrator ids."); + executor.execute( + () -> callback.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR)); + return; + } + mVibratorManager.startVendorSession(vibratorIds, attrs, reason, cancellationSignal, + executor, callback); + } + + @Nullable + private int[] getVibratorIds() { + synchronized (mLock) { + if (mVibratorIds != null) { + return mVibratorIds; + } + if (mVibratorManager == null) { + Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager."); + return null; + } + return mVibratorIds = mVibratorManager.getVibratorIds(); + } + } + /** * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener} * that were left registered to vibrators after failures to register them to all vibrators. @@ -319,8 +374,7 @@ public class SystemVibrator extends Vibrator { } /** Registers a listener to all individual vibrators in {@link VibratorManager}. */ - public void register(VibratorManager vibratorManager) { - int[] vibratorIds = vibratorManager.getVibratorIds(); + public void register(VibratorManager vibratorManager, @NonNull int[] vibratorIds) { synchronized (mLock) { for (int i = 0; i < vibratorIds.length; i++) { int vibratorId = vibratorIds[i]; diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java index a5697fb0e8a8..f9935d2870b0 100644 --- a/core/java/android/os/SystemVibratorManager.java +++ b/core/java/android/os/SystemVibratorManager.java @@ -22,6 +22,10 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.hardware.vibrator.IVibratorManager; +import android.os.vibrator.IVibrationSession; +import android.os.vibrator.IVibrationSessionCallback; +import android.os.vibrator.VendorVibrationSession; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; @@ -47,6 +51,8 @@ public class SystemVibratorManager extends VibratorManager { @GuardedBy("mLock") private int[] mVibratorIds; @GuardedBy("mLock") + private int mCapabilities; + @GuardedBy("mLock") private final SparseArray<Vibrator> mVibrators = new SparseArray<>(); @GuardedBy("mLock") @@ -84,6 +90,11 @@ public class SystemVibratorManager extends VibratorManager { } } + @Override + public boolean hasCapabilities(int capabilities) { + return (getCapabilities() & capabilities) == capabilities; + } + @NonNull @Override public Vibrator getVibrator(int vibratorId) { @@ -173,7 +184,7 @@ public class SystemVibratorManager extends VibratorManager { int inputSource, String reason, int flags, int privFlags) { if (mService == null) { Log.w(TAG, "Failed to perform haptic feedback for input device;" - + " no vibrator manager service."); + + " no vibrator manager service."); return; } Trace.traceBegin(TRACE_TAG_VIBRATOR, "performHapticFeedbackForInputDevice"); @@ -197,6 +208,50 @@ public class SystemVibratorManager extends VibratorManager { cancelVibration(usageFilter); } + @Override + public void startVendorSession(@NonNull int[] vibratorIds, @NonNull VibrationAttributes attrs, + @Nullable String reason, @Nullable CancellationSignal cancellationSignal, + @NonNull Executor executor, @NonNull VendorVibrationSession.Callback callback) { + Objects.requireNonNull(vibratorIds); + VendorVibrationSessionCallbackDelegate callbackDelegate = + new VendorVibrationSessionCallbackDelegate(executor, callback); + if (mService == null) { + Log.w(TAG, "Failed to start vibration session; no vibrator manager service."); + callbackDelegate.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR); + return; + } + try { + ICancellationSignal remoteCancellationSignal = mService.startVendorVibrationSession( + mUid, mContext.getDeviceId(), mPackageName, vibratorIds, attrs, reason, + callbackDelegate); + if (cancellationSignal != null) { + cancellationSignal.setRemote(remoteCancellationSignal); + } + } catch (RemoteException e) { + Log.w(TAG, "Failed to start vibration session.", e); + callbackDelegate.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR); + } + } + + private int getCapabilities() { + synchronized (mLock) { + if (mCapabilities != 0) { + return mCapabilities; + } + try { + if (mService == null) { + Log.w(TAG, "Failed to retrieve vibrator manager capabilities;" + + " no vibrator manager service."); + } else { + return mCapabilities = mService.getCapabilities(); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return 0; + } + } + private void cancelVibration(int usageFilter) { if (mService == null) { Log.w(TAG, "Failed to cancel vibration; no vibrator manager service."); @@ -228,12 +283,45 @@ public class SystemVibratorManager extends VibratorManager { } } + /** Callback for vendor vibration sessions. */ + private static class VendorVibrationSessionCallbackDelegate extends + IVibrationSessionCallback.Stub { + private final Executor mExecutor; + private final VendorVibrationSession.Callback mCallback; + + VendorVibrationSessionCallbackDelegate( + @NonNull Executor executor, + @NonNull VendorVibrationSession.Callback callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onStarted(IVibrationSession session) { + mExecutor.execute(() -> mCallback.onStarted(new VendorVibrationSession(session))); + } + + @Override + public void onFinishing() { + mExecutor.execute(() -> mCallback.onFinishing()); + } + + @Override + public void onFinished(int status) { + mExecutor.execute(() -> mCallback.onFinished(status)); + } + } + /** Controls vibrations on a single vibrator. */ private final class SingleVibrator extends Vibrator { private final VibratorInfo mVibratorInfo; + private final int[] mVibratorId; SingleVibrator(@NonNull VibratorInfo vibratorInfo) { mVibratorInfo = vibratorInfo; + mVibratorId = new int[]{mVibratorInfo.getId()}; } @Override @@ -252,6 +340,11 @@ public class SystemVibratorManager extends VibratorManager { } @Override + public boolean areVendorSessionsSupported() { + return SystemVibratorManager.this.hasCapabilities(IVibratorManager.CAP_START_SESSIONS); + } + + @Override public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, @Nullable VibrationEffect effect, @Nullable VibrationAttributes attrs) { CombinedVibration combined = CombinedVibration.startParallel() @@ -369,5 +462,13 @@ public class SystemVibratorManager extends VibratorManager { } } } + + @Override + public void startVendorSession(@NonNull VibrationAttributes attrs, String reason, + @Nullable CancellationSignal cancellationSignal, @NonNull Executor executor, + @NonNull VendorVibrationSession.Callback callback) { + SystemVibratorManager.this.startVendorSession(mVibratorId, attrs, reason, + cancellationSignal, executor, callback); + } } } diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index c4c4580bf0a8..53f8a9267499 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -33,6 +33,7 @@ import android.content.res.Resources; import android.hardware.vibrator.IVibrator; import android.media.AudioAttributes; import android.os.vibrator.Flags; +import android.os.vibrator.VendorVibrationSession; import android.os.vibrator.VibrationConfig; import android.os.vibrator.VibratorFrequencyProfile; import android.os.vibrator.VibratorFrequencyProfileLegacy; @@ -247,6 +248,34 @@ public abstract class Vibrator { } /** + * Check whether the vibrator has support for vendor-specific effects. + * + * <p>Vendor vibration effects can be created via {@link VibrationEffect#createVendorEffect}. + * + * @return True if the hardware can play vendor-specific vibration effects, false otherwise. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public boolean areVendorEffectsSupported() { + return getInfo().hasCapability(IVibrator.CAP_PERFORM_VENDOR_EFFECTS); + } + + /** + * Check whether the vibrator has support for vendor-specific vibration sessions. + * + * <p>Vendor vibration sessions can be started via {@link #startVendorSession}. + * + * @return True if the hardware can play vendor-specific vibration sessions, false otherwise. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public boolean areVendorSessionsSupported() { + return false; + } + + /** * Check whether the vibrator can be controlled by an external service with the * {@link IExternalVibratorService}. * @@ -922,4 +951,44 @@ public abstract class Vibrator { @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { } + + /** + * Starts a vibration session in this vibrator. + * + * <p>The session will start asynchronously once the vibrator control can be acquired. Once it's + * started the {@link VendorVibrationSession} will be provided to the callback. This session + * should be used to play vibrations until the session is ended or canceled. + * + * <p>The vendor app will have exclusive control over the vibrator during this session. This + * control can be revoked by the vibrator service, which will be notified to the same session + * callback with the {@link VendorVibrationSession#STATUS_CANCELED}. + * + * <p>The {@link VibrationAttributes} will be used to decide the priority of the vendor + * vibrations that will be performed in this session. All vibrations within this session will + * apply the same attributes. + * + * @param attrs The {@link VibrationAttributes} corresponding to the vibrations that will be + * performed in the session. This will be used to decide the priority of this + * session against other system vibrations. + * @param reason The description for this session, used for debugging purposes. + * @param cancellationSignal A signal to cancel the session before it starts. + * @param executor The executor for the session callbacks. + * @param callback The {@link VendorVibrationSession.Callback} for the started session. + * + * @see VendorVibrationSession + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + @RequiresPermission(allOf = { + android.Manifest.permission.VIBRATE, + android.Manifest.permission.VIBRATE_VENDOR_EFFECTS, + android.Manifest.permission.START_VIBRATION_SESSIONS, + }) + public void startVendorSession(@NonNull VibrationAttributes attrs, @Nullable String reason, + @Nullable CancellationSignal cancellationSignal, @NonNull Executor executor, + @NonNull VendorVibrationSession.Callback callback) { + Log.w(TAG, "startVendorSession is not supported"); + executor.execute(() -> callback.onFinished(VendorVibrationSession.STATUS_UNSUPPORTED)); + } } diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java index 0428876891f9..0072bc22ad8f 100644 --- a/core/java/android/os/VibratorManager.java +++ b/core/java/android/os/VibratorManager.java @@ -22,9 +22,12 @@ import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.app.ActivityThread; import android.content.Context; +import android.os.vibrator.VendorVibrationSession; import android.util.Log; import android.view.HapticFeedbackConstants; +import java.util.concurrent.Executor; + /** * Provides access to all vibrators from the device, as well as the ability to run them * in a synchronized fashion. @@ -62,6 +65,14 @@ public abstract class VibratorManager { public abstract int[] getVibratorIds(); /** + * Return true if the vibrator manager has all capabilities, false otherwise. + * @hide + */ + public boolean hasCapabilities(int capabilities) { + return false; + } + + /** * Retrieve a single vibrator by id. * * @param vibratorId The id of the vibrator to be retrieved. @@ -190,4 +201,30 @@ public abstract class VibratorManager { */ @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel(int usageFilter); + + + /** + * Starts a vibration session on given vibrators. + * + * @param vibratorIds The vibrators that will be controlled by this session. + * @param attrs The {@link VibrationAttributes} corresponding to the vibrations that will + * be performed in the session. This will be used to decide the priority of + * this session against other system vibrations. + * @param reason The description for this session, used for debugging purposes. + * @param cancellationSignal A signal to cancel the session before it starts. + * @param executor The executor for the session callbacks. + * @param callback The {@link VendorVibrationSession.Callback} for the started session. + * @see Vibrator#startVendorSession + * @hide + */ + @RequiresPermission(allOf = { + android.Manifest.permission.VIBRATE, + android.Manifest.permission.VIBRATE_VENDOR_EFFECTS, + android.Manifest.permission.START_VIBRATION_SESSIONS, + }) + public void startVendorSession(@NonNull int[] vibratorIds, @NonNull VibrationAttributes attrs, + @Nullable String reason, @Nullable CancellationSignal cancellationSignal, + @NonNull Executor executor, @NonNull VendorVibrationSession.Callback callback) { + Log.w(TAG, "startVendorSession is not supported"); + } } diff --git a/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl b/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl new file mode 100644 index 000000000000..dbe54891b0f2 --- /dev/null +++ b/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl @@ -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 android.os.instrumentation; + +/** + * Represents the location of the code for a compiled method within a process' + * memory. + * {@hide} + */ +@JavaDerive(toString=true) +parcelable ExecutableMethodFileOffsets { + /** + * The OS path of the containing file (could be virtual). + */ + @utf8InCpp String containerPath; + /** + * The offset of the containing file within the process' memory. + */ + long containerOffset; + /** + * The offset of the method within the containing file. + */ + long methodOffset; +} diff --git a/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl new file mode 100644 index 000000000000..c45c51d15cc9 --- /dev/null +++ b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.instrumentation; + +import android.os.instrumentation.ExecutableMethodFileOffsets; +import android.os.instrumentation.MethodDescriptor; +import android.os.instrumentation.TargetProcess; + +/** + * System private API for managing the dynamic attachment of instrumentation. + * + * {@hide} + */ +interface IDynamicInstrumentationManager { + /** Provides ART metadata about the described compiled method within the target process */ + @PermissionManuallyEnforced + @nullable ExecutableMethodFileOffsets getExecutableMethodFileOffsets( + in TargetProcess targetProcess, in MethodDescriptor methodDescriptor); +} diff --git a/core/java/android/os/instrumentation/MethodDescriptor.aidl b/core/java/android/os/instrumentation/MethodDescriptor.aidl new file mode 100644 index 000000000000..055d0ecb66e4 --- /dev/null +++ b/core/java/android/os/instrumentation/MethodDescriptor.aidl @@ -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 android.os.instrumentation; + +/** + * Represents a JVM method, where class fields that make up its signature. + * {@hide} + */ +@JavaDerive(toString=true) +parcelable MethodDescriptor { + /** + * Fully qualified class in reverse.domain.Naming + */ + @utf8InCpp String fullyQualifiedClassName; + /** + * Name of the method. + */ + @utf8InCpp String methodName; + /** + * Fully qualified types of method parameters, or string representations if primitive e.g. "int". + */ + @utf8InCpp String[] fullyQualifiedParameters; +} diff --git a/core/java/android/os/instrumentation/TargetProcess.aidl b/core/java/android/os/instrumentation/TargetProcess.aidl new file mode 100644 index 000000000000..e90780d07ef2 --- /dev/null +++ b/core/java/android/os/instrumentation/TargetProcess.aidl @@ -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 android.os.instrumentation; + +/** + * Addresses a process that would run on the device. + * Helps disambiguate targeted processes in cases of pid re-use. + * {@hide} + */ +@JavaDerive(toString=true) +parcelable TargetProcess { + int uid; + int pid; + @utf8InCpp String processName; +} diff --git a/core/java/android/os/vibrator/IVibrationSession.aidl b/core/java/android/os/vibrator/IVibrationSession.aidl new file mode 100644 index 000000000000..e8295492665d --- /dev/null +++ b/core/java/android/os/vibrator/IVibrationSession.aidl @@ -0,0 +1,55 @@ +/** + * 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.vibrator; + +import android.os.CombinedVibration; + +/** + * The communication channel by which an app control the system vibrators. + * + * In order to synchronize the places where vibrations might be controlled we provide this interface + * so the vibrator subsystem has a chance to: + * + * 1) Decide whether the current session should have the vibrator control. + * 2) Stop any on-going session for a new session/vibration, based on current system policy. + * {@hide} + */ +interface IVibrationSession { + const int STATUS_UNKNOWN = 0; + const int STATUS_SUCCESS = 1; + const int STATUS_IGNORED = 2; + const int STATUS_UNSUPPORTED = 3; + const int STATUS_CANCELED = 4; + const int STATUS_UNKNOWN_ERROR = 5; + + /** + * A method called to start a vibration within this session. This will fail if the session + * is finishing or was canceled. + */ + void vibrate(in CombinedVibration vibration, String reason); + + /** + * A method called by the app to stop this session gracefully. The vibrator will complete any + * ongoing vibration before the session is ended. + */ + void finishSession(); + + /** + * A method called by the app to stop this session immediatelly by interrupting any ongoing + * vibration. + */ + void cancelSession(); +} diff --git a/core/java/android/os/vibrator/IVibrationSessionCallback.aidl b/core/java/android/os/vibrator/IVibrationSessionCallback.aidl new file mode 100644 index 000000000000..36c3695a1bfe --- /dev/null +++ b/core/java/android/os/vibrator/IVibrationSessionCallback.aidl @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2024, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.os.vibrator; + +import android.os.vibrator.IVibrationSession; + +/** + * Callback for vibration session state. + * {@hide} + */ +oneway interface IVibrationSessionCallback { + + /** + * A method called by the service after a vibration session has successfully started. After this + * is called the app has control over the vibrator through this given session. + */ + void onStarted(in IVibrationSession session); + + /** + * A method called by the service to indicate the session is ending and should no longer receive + * vibration requests. + */ + void onFinishing(); + + /** + * A method called by the service after the session has ended. This might be triggered by the + * app or the service. The status code indicates the end reason. + */ + void onFinished(int status); +} diff --git a/core/java/android/os/vibrator/VendorVibrationSession.java b/core/java/android/os/vibrator/VendorVibrationSession.java new file mode 100644 index 000000000000..c23f2ed1a303 --- /dev/null +++ b/core/java/android/os/vibrator/VendorVibrationSession.java @@ -0,0 +1,236 @@ +/* + * 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.vibrator; + +import static android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS; + +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.os.CombinedVibration; +import android.os.RemoteException; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * A vendor session that temporarily gains control over the system vibrators. + * + * <p>Vibration effects can be played by the vibrator in a vendor session via {@link #vibrate}. The + * effects will be forwarded to the vibrator hardware immediately. Any concurrency support is + * defined and controlled by the vibrator hardware implementation. + * + * <p>The session should be ended by {@link #close()}, which will wait until the last vibration ends + * and the vibrator is released. The end of the session will be notified to the {@link Callback} + * provided when the session was created. + * + * <p>Any ongoing session can be immediately interrupted by the vendor app via {@link #cancel()}, + * including after {@link #close()} was called and the session is tearing down. A session can also + * be canceled by the vibrator service when it needs to regain control of the system vibrators. + * + * @see Vibrator#startVendorSession + * @hide + */ +@FlaggedApi(FLAG_VENDOR_VIBRATION_EFFECTS) +@SystemApi +public final class VendorVibrationSession implements AutoCloseable { + private static final String TAG = "VendorVibrationSession"; + + /** + * The session ended successfully. + */ + public static final int STATUS_SUCCESS = IVibrationSession.STATUS_SUCCESS; + + /** + * The session was ignored. + * + * <p>This might be caused by user settings, vibration policies or the device state that + * prevents the app from performing vibrations for the requested + * {@link android.os.VibrationAttributes}. + */ + public static final int STATUS_IGNORED = IVibrationSession.STATUS_IGNORED; + + /** + * The session is not supported. + * + * <p>The support for vendor vibration sessions can be checked via + * {@link Vibrator#areVendorSessionsSupported()}. + */ + public static final int STATUS_UNSUPPORTED = IVibrationSession.STATUS_UNSUPPORTED; + + /** + * The session was canceled. + * + * <p>This might be triggered by the app after a session starts via {@link #cancel()}, or it + * can be triggered by the platform before or after the session has started. + */ + public static final int STATUS_CANCELED = IVibrationSession.STATUS_CANCELED; + + /** + * The session status is unknown. + */ + public static final int STATUS_UNKNOWN = IVibrationSession.STATUS_UNKNOWN; + + /** + * The session failed with unknown error. + * + * <p>This can be caused by a failure to start a vibration session or after it has started, to + * indicate it has ended unexpectedly because of a system failure. + */ + public static final int STATUS_UNKNOWN_ERROR = IVibrationSession.STATUS_UNKNOWN_ERROR; + + /** @hide */ + @IntDef(prefix = { "STATUS_" }, value = { + STATUS_SUCCESS, + STATUS_IGNORED, + STATUS_UNSUPPORTED, + STATUS_CANCELED, + STATUS_UNKNOWN, + STATUS_UNKNOWN_ERROR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Status{} + + private final IVibrationSession mSession; + + /** @hide */ + public VendorVibrationSession(@NonNull IVibrationSession session) { + Objects.requireNonNull(session); + mSession = session; + } + + /** + * Vibrate with a given effect. + * + * <p>The vibration will be sent to the vibrator hardware immediately, without waiting for any + * previous vibration completion. The vendor should control the concurrency behavior at the + * hardware level (e.g. queueing, mixing, interrupting). + * + * <p>If the provided effect is played by the vibrator service with controlled timings (e.g. + * effects created via {@link VibrationEffect#createWaveform}), then triggering a new vibration + * will cause the ongoing playback to be interrupted in favor of the new vibration. If the + * effect is broken down into multiple consecutive commands (e.g. large primitive compositions) + * then the hardware commands will be triggered in succession without waiting for the completion + * callback. + * + * <p>The vendor app is responsible for timing the session requests and the vibrator hardware + * implementation is free to handle concurrency with different policies. + * + * @param effect The {@link VibrationEffect} describing the vibration to be performed. + * @param reason The description for the vibration reason, for debugging purposes. + */ + @RequiresPermission(android.Manifest.permission.VIBRATE) + public void vibrate(@NonNull VibrationEffect effect, @Nullable String reason) { + try { + mSession.vibrate(CombinedVibration.createParallel(effect), reason); + } catch (RemoteException e) { + Log.w(TAG, "Failed to vibrate in a vendor vibration session.", e); + e.rethrowFromSystemServer(); + } + } + + /** + * Cancel ongoing session. + * + * <p>This will stop the vibration immediately and return the vibrator control to the + * platform. This can also be triggered after {@link #close()} to immediately release the + * vibrator. + * + * <p>This will trigger {@link VendorVibrationSession.Callback#onFinished} directly with + * {@link #STATUS_CANCELED}. + */ + public void cancel() { + try { + mSession.cancelSession(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to cancel vendor vibration session.", e); + e.rethrowFromSystemServer(); + } + } + + /** + * End ongoing session gracefully. + * + * <p>This might continue the vibration while it's ramping down and wrapping up the session + * in the vibrator hardware. No more vibration commands can be sent through this session + * after this method is called. + * + * <p>This will trigger {@link VendorVibrationSession.Callback#onFinishing()}. + */ + @Override + public void close() { + try { + mSession.finishSession(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to finish vendor vibration session.", e); + e.rethrowFromSystemServer(); + } + } + + /** + * Callbacks for {@link VendorVibrationSession} events. + * + * @see Vibrator#startVendorSession + * @see VendorVibrationSession + */ + public interface Callback { + + /** + * New session was successfully started. + * + * <p>The vendor app can interact with the vibrator using the + * {@link VendorVibrationSession} provided. + */ + void onStarted(@NonNull VendorVibrationSession session); + + /** + * The session is ending and finishing any pending vibrations. + * + * <p>This is only invoked after {@link #onStarted(VendorVibrationSession)}. It will be + * triggered by both {@link VendorVibrationSession#cancel()} and + * {@link VendorVibrationSession#close()}. This might also be triggered if the platform + * cancels the ongoing session. + * + * <p>Session vibrations might be still ongoing in the vibrator hardware but the app can + * no longer send commands through the session. A finishing session can still be immediately + * stopped via calls to {@link VendorVibrationSession.Callback#cancel()}. + */ + void onFinishing(); + + /** + * The session is finished. + * + * <p>The vibrator has finished any vibration and returned to the platform's control. This + * might be triggered by the vendor app or by the vibrator service. + * + * <p>If this is triggered before {@link #onStarted} then the session was finished before + * starting, either because it was cancelled or failed to start. If the session has already + * started then this will be triggered after {@link #onFinishing()} to indicate all session + * vibrations are complete and the vibrator is no longer under the session's control. + * + * @param status The session status. + */ + void onFinished(@VendorVibrationSession.Status int status); + } +} diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig index 72f2de805474..dfc11dcb5427 100644 --- a/core/java/android/service/dreams/flags.aconfig +++ b/core/java/android/service/dreams/flags.aconfig @@ -67,3 +67,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "cleanup_dream_settings_on_uninstall" + namespace: "systemui" + description: "Cleans up dream settings if dream package is uninstalled." + bug: "338210427" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/service/settings/preferences/GetValueRequest.aidl b/core/java/android/service/settings/preferences/GetValueRequest.aidl new file mode 100644 index 000000000000..2a0eb09aa2a4 --- /dev/null +++ b/core/java/android/service/settings/preferences/GetValueRequest.aidl @@ -0,0 +1,4 @@ +package android.service.settings.preferences; + +/** @hide */ +parcelable GetValueRequest;
\ No newline at end of file diff --git a/core/java/android/service/settings/preferences/GetValueRequest.java b/core/java/android/service/settings/preferences/GetValueRequest.java new file mode 100644 index 000000000000..4f82800d1855 --- /dev/null +++ b/core/java/android/service/settings/preferences/GetValueRequest.java @@ -0,0 +1,139 @@ +/* + * 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.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.android.settingslib.flags.Flags; + +import java.util.Objects; + +/** + * Request parameters to retrieve the current value of a Settings Preference. + * + * <p>This object passed to {@link SettingsPreferenceService#onGetPreferenceValue} will result + * in a {@link GetValueResult}. + * + * <ul> + * <li>{@link #getScreenKey} is a parameter to distinguish the container screen + * of a preference as a preference key may not be unique within its application. + * <li>{@link #getPreferenceKey} is a parameter to identify the preference for which the value is + * being requested. These keys will be unique with their Preference Screen, but may not be unique + * within their application, so it is required to pair this with {@link #getScreenKey} to + * ensure this request matches the intended target. + * </ul> + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class GetValueRequest implements Parcelable { + + @NonNull + private final String mScreenKey; + @NonNull + private final String mPreferenceKey; + + /** + * Returns the screen key of requested Preference. + */ + @NonNull + public String getScreenKey() { + return mScreenKey; + } + + /** + * Returns the key of requested Preference. + */ + @NonNull + public String getPreferenceKey() { + return mPreferenceKey; + } + + private GetValueRequest(@NonNull Builder builder) { + mScreenKey = builder.mScreenKey; + mPreferenceKey = builder.mPreferenceKey; + } + + private GetValueRequest(@NonNull Parcel in) { + mScreenKey = Objects.requireNonNull(in.readString8()); + mPreferenceKey = Objects.requireNonNull(in.readString8()); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mScreenKey); + dest.writeString8(mPreferenceKey); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link GetValueRequest}. + */ + @NonNull + public static final Creator<GetValueRequest> CREATOR = new Creator<GetValueRequest>() { + @Override + public GetValueRequest createFromParcel(@NonNull Parcel in) { + return new GetValueRequest(in); + } + + @Override + public GetValueRequest[] newArray(int size) { + return new GetValueRequest[size]; + } + }; + + /** + * Builder to construct {@link GetValueRequest}. + */ + public static final class Builder { + private final String mScreenKey; + private final String mPreferenceKey; + + /** + * Create Builder instance. + * @param screenKey required to be not empty + * @param preferenceKey required to be not empty + */ + public Builder(@NonNull String screenKey, @NonNull String preferenceKey) { + if (TextUtils.isEmpty(screenKey)) { + throw new IllegalArgumentException("screenKey cannot be empty"); + } + if (TextUtils.isEmpty(preferenceKey)) { + throw new IllegalArgumentException("preferenceKey cannot be empty"); + } + mScreenKey = screenKey; + mPreferenceKey = preferenceKey; + } + + /** + * Constructs an immutable {@link GetValueRequest} object. + */ + @NonNull + public GetValueRequest build() { + return new GetValueRequest(this); + } + } +} diff --git a/core/java/android/service/settings/preferences/GetValueResult.aidl b/core/java/android/service/settings/preferences/GetValueResult.aidl new file mode 100644 index 000000000000..b5ebd35a3a37 --- /dev/null +++ b/core/java/android/service/settings/preferences/GetValueResult.aidl @@ -0,0 +1,4 @@ +package android.service.settings.preferences; + +/** @hide */ +parcelable GetValueResult;
\ No newline at end of file diff --git a/core/java/android/service/settings/preferences/GetValueResult.java b/core/java/android/service/settings/preferences/GetValueResult.java new file mode 100644 index 000000000000..369dea77cc85 --- /dev/null +++ b/core/java/android/service/settings/preferences/GetValueResult.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settingslib.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Result object given a corresponding {@link GetValueRequest}. + * <ul> + * <li>If the request was successful, {@link #getResultCode} will be {@link #RESULT_OK}, + * {@link #getValue} will be populated with the settings preference value and + * {@link #getMetadata} will be populated with its metadata. + * <li>If the request is unsuccessful, {@link #getResultCode} be a value other than + * {@link #RESULT_OK} - see documentation for those possibilities to understand the cause + * of the failure. + * </ul> + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class GetValueResult implements Parcelable { + + @ResultCode + private final int mResultCode; + @Nullable + private final SettingsPreferenceValue mValue; + @Nullable + private final SettingsPreferenceMetadata mMetadata; + + /** + * Returns the result code indicating status of the request. + */ + @ResultCode + public int getResultCode() { + return mResultCode; + } + + /** + * Returns the value of requested Preference if request successful. + */ + @Nullable + public SettingsPreferenceValue getValue() { + return mValue; + } + + /** + * Returns the metadata of requested Preference if request successful. + */ + @Nullable + public SettingsPreferenceMetadata getMetadata() { + return mMetadata; + } + + /** @hide */ + @IntDef(prefix = { "RESULT_" }, value = { + RESULT_OK, + RESULT_UNSUPPORTED, + RESULT_UNAVAILABLE, + RESULT_REQUIRE_APP_PERMISSION, + RESULT_DISALLOW, + RESULT_INVALID_REQUEST, + RESULT_INTERNAL_ERROR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ResultCode { + } + + /** Request is successful. */ + public static final int RESULT_OK = 0; + /** + * Requested preference is not supported by this API. + * <p>Retry not advised. + */ + public static final int RESULT_UNSUPPORTED = 1; + /** + * Preference is currently not available, likely due to device state or the state of + * a dependency. + * <p>Retry may succeed if underlying conditions change. + */ + public static final int RESULT_UNAVAILABLE = 2; + /** + * Requested preference requires permissions not held by the calling application. + * <p>Retry may succeed if necessary permissions are obtained. + */ + public static final int RESULT_REQUIRE_APP_PERMISSION = 3; + /** + * Requested preference is not allowed for access in this API under the current device policy. + * <p>Retry may succeed if underlying conditions change. + */ + public static final int RESULT_DISALLOW = 4; + /** + * Request object is not valid. + * <p>Retry not advised with current parameters. + */ + public static final int RESULT_INVALID_REQUEST = 5; + /** + * API call failed due to an issue with the service binding. + * <p>Retry may succeed. + */ + public static final int RESULT_INTERNAL_ERROR = 6; + + + private GetValueResult(@NonNull Builder builder) { + mResultCode = builder.mResultCode; + mValue = builder.mValue; + mMetadata = builder.mMetadata; + } + + private GetValueResult(@NonNull Parcel in) { + mResultCode = in.readInt(); + mValue = in.readParcelable(SettingsPreferenceValue.class.getClassLoader(), + SettingsPreferenceValue.class); + mMetadata = in.readParcelable(SettingsPreferenceMetadata.class.getClassLoader(), + SettingsPreferenceMetadata.class); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mResultCode); + dest.writeParcelable(mValue, flags); + dest.writeParcelable(mMetadata, flags); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link GetValueResult}. + */ + @NonNull + public static final Creator<GetValueResult> CREATOR = new Creator<>() { + @Override + public GetValueResult createFromParcel(@NonNull Parcel in) { + return new GetValueResult(in); + } + + @Override + public GetValueResult[] newArray(int size) { + return new GetValueResult[size]; + } + }; + + /** + * Builder to construct {@link GetValueResult}. + */ + public static final class Builder { + @ResultCode + private final int mResultCode; + private SettingsPreferenceValue mValue; + private SettingsPreferenceMetadata mMetadata; + + /** + * Create Builder instance. + * @param resultCode indicates status of the request + */ + public Builder(@ResultCode int resultCode) { + mResultCode = resultCode; + } + + /** + * Sets the preference value on the result. + */ + @NonNull + public Builder setValue(@Nullable SettingsPreferenceValue value) { + mValue = value; + return this; + } + + /** + * Sets the metadata on the result. + */ + @NonNull + public Builder setMetadata(@Nullable SettingsPreferenceMetadata metadata) { + mMetadata = metadata; + return this; + } + + /** + * Constructs an immutable {@link GetValueResult} object. + */ + @NonNull + public GetValueResult build() { + return new GetValueResult(this); + } + } +} diff --git a/core/java/android/service/settings/preferences/IGetValueCallback.aidl b/core/java/android/service/settings/preferences/IGetValueCallback.aidl new file mode 100644 index 000000000000..bbc7423f453e --- /dev/null +++ b/core/java/android/service/settings/preferences/IGetValueCallback.aidl @@ -0,0 +1,9 @@ +package android.service.settings.preferences; + +import android.service.settings.preferences.GetValueResult; + +/** @hide */ +oneway interface IGetValueCallback { + void onSuccess(in GetValueResult result) = 1; + void onFailure() = 2; +} diff --git a/core/java/android/service/settings/preferences/IMetadataCallback.aidl b/core/java/android/service/settings/preferences/IMetadataCallback.aidl new file mode 100644 index 000000000000..3bd5ebe93660 --- /dev/null +++ b/core/java/android/service/settings/preferences/IMetadataCallback.aidl @@ -0,0 +1,9 @@ +package android.service.settings.preferences; + +import android.service.settings.preferences.MetadataResult; + +/** @hide */ +oneway interface IMetadataCallback { + void onSuccess(in MetadataResult result); + void onFailure(); +} diff --git a/core/java/android/service/settings/preferences/ISetValueCallback.aidl b/core/java/android/service/settings/preferences/ISetValueCallback.aidl new file mode 100644 index 000000000000..0765660c83c3 --- /dev/null +++ b/core/java/android/service/settings/preferences/ISetValueCallback.aidl @@ -0,0 +1,9 @@ +package android.service.settings.preferences; + +import android.service.settings.preferences.SetValueResult; + +/** @hide */ +oneway interface ISetValueCallback { + void onSuccess(in SetValueResult result); + void onFailure(); +} diff --git a/core/java/android/service/settings/preferences/MetadataRequest.aidl b/core/java/android/service/settings/preferences/MetadataRequest.aidl new file mode 100644 index 000000000000..dc3cbc42661e --- /dev/null +++ b/core/java/android/service/settings/preferences/MetadataRequest.aidl @@ -0,0 +1,4 @@ +package android.service.settings.preferences; + +/** @hide */ +parcelable MetadataRequest;
\ No newline at end of file diff --git a/core/java/android/service/settings/preferences/MetadataRequest.java b/core/java/android/service/settings/preferences/MetadataRequest.java new file mode 100644 index 000000000000..ffecc6bec5b2 --- /dev/null +++ b/core/java/android/service/settings/preferences/MetadataRequest.java @@ -0,0 +1,75 @@ +/* + * 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.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.android.settingslib.flags.Flags; + +/** + * Request parameters to retrieve all metadata for all available settings preferences within this + * application. + * + * <p>This object passed to {@link SettingsPreferenceService#onGetAllPreferenceMetadata} will result + * in a {@link MetadataResult}. + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class MetadataRequest implements Parcelable { + private MetadataRequest() {} + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link MetadataRequest}. + */ + @NonNull + public static final Creator<MetadataRequest> CREATOR = new Creator<>() { + @Override + public MetadataRequest createFromParcel(@NonNull Parcel in) { + return new MetadataRequest(); + } + + @Override + public MetadataRequest[] newArray(int size) { + return new MetadataRequest[size]; + } + }; + + /** + * Builder to construct {@link MetadataRequest}. + */ + public static final class Builder { + /** Constructs an immutable {@link MetadataRequest} object. */ + @NonNull + public MetadataRequest build() { + return new MetadataRequest(); + } + } +} diff --git a/core/java/android/service/settings/preferences/MetadataResult.aidl b/core/java/android/service/settings/preferences/MetadataResult.aidl new file mode 100644 index 000000000000..af9e8a86e3ab --- /dev/null +++ b/core/java/android/service/settings/preferences/MetadataResult.aidl @@ -0,0 +1,4 @@ +package android.service.settings.preferences; + +/** @hide */ +parcelable MetadataResult;
\ No newline at end of file diff --git a/core/java/android/service/settings/preferences/MetadataResult.java b/core/java/android/service/settings/preferences/MetadataResult.java new file mode 100644 index 000000000000..6a65dcc9c757 --- /dev/null +++ b/core/java/android/service/settings/preferences/MetadataResult.java @@ -0,0 +1,164 @@ +/* + * 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.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.android.settingslib.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Result object given a corresponding {@link MetadataRequest}. + * <ul> + * <li>If the request was successful, {@link #getResultCode} will be {@link #RESULT_OK} and + * {@link #getMetadataList} will be populated with metadata for all available preferences within + * this application. + * <li>If the request is unsuccessful, {@link #getResultCode} be a value other than + * {@link #RESULT_OK} - see documentation for those possibilities to understand the cause + * of the failure. + * </ul> + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class MetadataResult implements Parcelable { + + @ResultCode + private final int mResultCode; + @NonNull + private final List<SettingsPreferenceMetadata> mMetadataList; + + /** + * Returns the result code indicating status of the request. + */ + @ResultCode + public int getResultCode() { + return mResultCode; + } + + /** + * Returns the list of available Preference Metadata. + * <p>This instance is shared so this list should not be modified. + */ + @NonNull + public List<SettingsPreferenceMetadata> getMetadataList() { + return mMetadataList; + } + + /** @hide */ + @IntDef(prefix = { "RESULT_" }, value = { + RESULT_OK, + RESULT_UNSUPPORTED, + RESULT_INTERNAL_ERROR + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ResultCode { + } + + /** Request is successful. */ + public static final int RESULT_OK = 0; + /** + * No preferences in this application support this API. + * <p>Retry not advised. + */ + public static final int RESULT_UNSUPPORTED = 1; + /** + * API call failed due to an issue with the service binding. + * <p>Retry may succeed. + */ + public static final int RESULT_INTERNAL_ERROR = 2; + + private MetadataResult(@NonNull Builder builder) { + mResultCode = builder.mResultCode; + mMetadataList = builder.mMetadataList; + } + private MetadataResult(@NonNull Parcel in) { + mResultCode = in.readInt(); + mMetadataList = new ArrayList<>(); + in.readTypedList(mMetadataList, SettingsPreferenceMetadata.CREATOR); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mResultCode); + dest.writeTypedList(mMetadataList, flags); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link MetadataResult}. + */ + @NonNull + public static final Creator<MetadataResult> CREATOR = new Creator<>() { + @Override + public MetadataResult createFromParcel(@NonNull Parcel in) { + return new MetadataResult(in); + } + + @Override + public MetadataResult[] newArray(int size) { + return new MetadataResult[size]; + } + }; + + /** + * Builder to construct {@link MetadataResult}. + */ + public static final class Builder { + @ResultCode + private final int mResultCode; + private List<SettingsPreferenceMetadata> mMetadataList = Collections.emptyList(); + + /** + * Create Builder instance. + * @param resultCode indicates status of the request + */ + public Builder(@ResultCode int resultCode) { + mResultCode = resultCode; + } + + /** + * Sets the metadata list on the result. + */ + @NonNull + public Builder setMetadataList(@NonNull List<SettingsPreferenceMetadata> metadataList) { + mMetadataList = metadataList; + return this; + } + + /** + * Constructs an immutable {@link MetadataResult} object. + */ + @NonNull + public MetadataResult build() { + return new MetadataResult(this); + } + } +} diff --git a/core/java/android/service/settings/preferences/SetValueRequest.aidl b/core/java/android/service/settings/preferences/SetValueRequest.aidl new file mode 100644 index 000000000000..198e333d5cb6 --- /dev/null +++ b/core/java/android/service/settings/preferences/SetValueRequest.aidl @@ -0,0 +1,4 @@ +package android.service.settings.preferences; + +/** @hide */ +parcelable SetValueRequest;
\ No newline at end of file diff --git a/core/java/android/service/settings/preferences/SetValueRequest.java b/core/java/android/service/settings/preferences/SetValueRequest.java new file mode 100644 index 000000000000..f7600aecdfaf --- /dev/null +++ b/core/java/android/service/settings/preferences/SetValueRequest.java @@ -0,0 +1,158 @@ +/* + * 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.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.android.settingslib.flags.Flags; + +import java.util.Objects; + +/** + * Request parameters to set the current value to a Settings Preference. + * <p>This object passed to {@link SettingsPreferenceService#onSetPreferenceValue} will result in a + * {@link SetValueResult}. + * <ul> + * <li>{@link #getScreenKey} is a parameter to distinguish the container screen + * of a preference as a preference key may not be unique within its application. + * <li>{@link #getPreferenceKey} is a parameter to identify the preference for which the value is + * being requested. These keys will be unique with their Preference Screen, but may not be unique + * within their application, so it is required to pair this with {@link #getScreenKey} to + * ensure this request matches the intended target. + * <li>{@link #getPreferenceValue} is a parameter to specify the value that this request aims to + * set. If this value is invalid (malformed or does not match the type of the preference) then + * this request will fail. + * </ul> + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class SetValueRequest implements Parcelable { + + @NonNull + private final String mScreenKey; + @NonNull + private final String mPreferenceKey; + @NonNull + private final SettingsPreferenceValue mPreferenceValue; + + /** + * Returns the screen key of requested Preference. + */ + @NonNull + public String getScreenKey() { + return mScreenKey; + } + + /** + * Returns the key of requested Preference. + */ + @NonNull + public String getPreferenceKey() { + return mPreferenceKey; + } + + /** + * Returns the value of requested Preference. + */ + @NonNull + public SettingsPreferenceValue getPreferenceValue() { + return mPreferenceValue; + } + + private SetValueRequest(@NonNull Builder builder) { + mScreenKey = builder.mScreenKey; + mPreferenceKey = builder.mPreferenceKey; + mPreferenceValue = builder.mPreferenceValue; + } + + private SetValueRequest(@NonNull Parcel in) { + mScreenKey = Objects.requireNonNull(in.readString8()); + mPreferenceKey = Objects.requireNonNull(in.readString8()); + mPreferenceValue = Objects.requireNonNull(in.readParcelable( + SettingsPreferenceValue.class.getClassLoader(), SettingsPreferenceValue.class)); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mScreenKey); + dest.writeString8(mPreferenceKey); + dest.writeParcelable(mPreferenceValue, flags); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link SetValueRequest}. + */ + @NonNull + public static final Creator<SetValueRequest> CREATOR = new Creator<SetValueRequest>() { + @Override + public SetValueRequest createFromParcel(@NonNull Parcel in) { + return new SetValueRequest(in); + } + + @Override + public SetValueRequest[] newArray(int size) { + return new SetValueRequest[size]; + } + }; + + /** + * Builder to construct {@link SetValueRequest}. + */ + public static final class Builder { + private final String mScreenKey; + private final String mPreferenceKey; + private final SettingsPreferenceValue mPreferenceValue; + + /** + * Create Builder instance. + * @param screenKey required to be not empty + * @param preferenceKey required to be not empty + * @param value value to set to requested Preference + */ + public Builder(@NonNull String screenKey, @NonNull String preferenceKey, + @NonNull SettingsPreferenceValue value) { + if (TextUtils.isEmpty(screenKey)) { + throw new IllegalArgumentException("screenKey cannot be empty"); + } + if (TextUtils.isEmpty(preferenceKey)) { + throw new IllegalArgumentException("preferenceKey cannot be empty"); + } + mScreenKey = screenKey; + mPreferenceKey = preferenceKey; + mPreferenceValue = value; + } + + /** + * Constructs an immutable {@link SetValueRequest} object. + */ + @NonNull + public SetValueRequest build() { + return new SetValueRequest(this); + } + } +} diff --git a/core/java/android/service/settings/preferences/SetValueResult.aidl b/core/java/android/service/settings/preferences/SetValueResult.aidl new file mode 100644 index 000000000000..f54813484d68 --- /dev/null +++ b/core/java/android/service/settings/preferences/SetValueResult.aidl @@ -0,0 +1,4 @@ +package android.service.settings.preferences; + +/** @hide */ +parcelable SetValueResult;
\ No newline at end of file diff --git a/core/java/android/service/settings/preferences/SetValueResult.java b/core/java/android/service/settings/preferences/SetValueResult.java new file mode 100644 index 000000000000..cb1776abd3bc --- /dev/null +++ b/core/java/android/service/settings/preferences/SetValueResult.java @@ -0,0 +1,179 @@ +/* + * 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.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.android.settingslib.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Result object given a corresponding {@link SetValueRequest}. + * <ul> + * <li>If the request was successful, {@link #getResultCode} will be {@link #RESULT_OK}. + * <li>If the request is unsuccessful, {@link #getResultCode} be a value other than + * {@link #RESULT_OK} - see documentation for those possibilities to understand the cause + * of the failure. + * </ul> + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class SetValueResult implements Parcelable { + + @ResultCode + private final int mResultCode; + + /** + * Returns the result code indicating status of the request. + */ + @ResultCode + public int getResultCode() { + return mResultCode; + } + + /** @hide */ + @IntDef(prefix = { "RESULT_" }, value = { + RESULT_OK, + RESULT_UNSUPPORTED, + RESULT_DISABLED, + RESULT_RESTRICTED, + RESULT_UNAVAILABLE, + RESULT_REQUIRE_APP_PERMISSION, + RESULT_REQUIRE_USER_CONSENT, + RESULT_DISALLOW, + RESULT_INVALID_REQUEST, + RESULT_INTERNAL_ERROR + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ResultCode { + } + + /** Request is successful and the value was set. */ + public static final int RESULT_OK = 0; + /** + * Requested preference is not supported by this API. + * <p>Retry not advised. + */ + public static final int RESULT_UNSUPPORTED = 1; + /** + * Requested preference is disabled, thus unable to be set in this state. + * <p>Retry may succeed if underlying conditions change. + */ + public static final int RESULT_DISABLED = 2; + /** + * Requested preference is restricted, thus unable to be set under this policy. + * <p>Retry may succeed if underlying conditions change. + */ + public static final int RESULT_RESTRICTED = 3; + /** + * Preference is currently not available, likely due to device state or the state of + * a dependency. + * <p>Retry may succeed if underlying conditions change. + */ + public static final int RESULT_UNAVAILABLE = 4; + /** + * Requested preference requires permissions not held by the calling application. + * <p>Retry may succeed if necessary permissions are obtained. + */ + public static final int RESULT_REQUIRE_APP_PERMISSION = 5; + /** + * User consent was not approved for this operation. + * <p>Retry may succeed if user provides consent. + */ + public static final int RESULT_REQUIRE_USER_CONSENT = 6; + /** + * Requested preference is not allowed for access in this API under the current device policy. + * <p>Retry may succeed if underlying conditions change. + */ + public static final int RESULT_DISALLOW = 7; + /** + * Request object is not valid. + * <p>Retry not advised with current parameters. + */ + public static final int RESULT_INVALID_REQUEST = 8; + /** + * API call failed due to an issue with the service binding. + * <p>Retry may succeed. + */ + public static final int RESULT_INTERNAL_ERROR = 9; + + private SetValueResult(@NonNull Builder builder) { + mResultCode = builder.mResultCode; + } + + private SetValueResult(@NonNull Parcel in) { + mResultCode = in.readInt(); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mResultCode); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link SetValueResult}. + */ + @NonNull + public static final Creator<SetValueResult> CREATOR = new Creator<>() { + @Override + public SetValueResult createFromParcel(@NonNull Parcel in) { + return new SetValueResult(in); + } + + @Override + public SetValueResult[] newArray(int size) { + return new SetValueResult[size]; + } + }; + + /** + * Builder to construct {@link SetValueResult}. + */ + public static final class Builder { + @ResultCode + private final int mResultCode; + + /** + * Create Builder instance. + * @param resultCode indicates status of the request + */ + public Builder(@ResultCode int resultCode) { + mResultCode = resultCode; + } + + /** + * Constructs an immutable {@link SetValueResult} object. + */ + @NonNull + public SetValueResult build() { + return new SetValueResult(this); + } + } +} diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java new file mode 100644 index 000000000000..1d08c5217129 --- /dev/null +++ b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java @@ -0,0 +1,436 @@ +/* + * 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.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.SuppressLint; +import android.app.PendingIntent; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settingslib.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Data object representation of a Settings Preference definition and state. + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class SettingsPreferenceMetadata implements Parcelable { + + @NonNull + private final String mKey; + @NonNull + private final String mScreenKey; + @Nullable + private final String mTitle; + @Nullable + private final String mSummary; + @NonNull + private final List<String> mBreadcrumbs; + @NonNull + private final List<String> mReadPermissions; + @NonNull + private final List<String> mWritePermissions; + private final boolean mEnabled; + private final boolean mAvailable; + private final boolean mWritable; + private final boolean mRestricted; + private final int mSensitivity; + @Nullable + private final PendingIntent mLaunchIntent; + @NonNull + private final Bundle mExtras; + + /** + * Returns the key of Preference. + */ + @NonNull + public String getKey() { + return mKey; + } + + /** + * Returns the screen key of Preference. + */ + @NonNull + public String getScreenKey() { + return mScreenKey; + } + + /** + * Returns the title of Preference. + */ + @Nullable + public String getTitle() { + return mTitle; + } + + /** + * Returns the summary of Preference. + */ + @Nullable + public String getSummary() { + return mSummary; + } + + /** + * Returns the breadcrumbs (navigation context) of Preference. + * <p>May be empty. + */ + @NonNull + public List<String> getBreadcrumbs() { + return mBreadcrumbs; + } + + /** + * Returns the permissions required to read this Preference's value. + * <p>May be empty. + */ + @NonNull + public List<String> getReadPermissions() { + return mReadPermissions; + } + + /** + * Returns the permissions required to write this Preference's value. + * <p>May be empty. + */ + @NonNull + public List<String> getWritePermissions() { + return mWritePermissions; + } + + /** + * Returns whether Preference is enabled. + */ + public boolean isEnabled() { + return mEnabled; + } + + /** + * Returns whether Preference is available. + */ + public boolean isAvailable() { + return mAvailable; + } + + /** + * Returns whether Preference is writable. + */ + public boolean isWritable() { + return mWritable; + } + + /** + * Returns whether Preference is restricted. + */ + public boolean isRestricted() { + return mRestricted; + } + + /** + * Returns the write-level sensitivity of Preference. + */ + @WriteSensitivity + public int getWriteSensitivity() { + return mSensitivity; + } + + /** + * Returns the intent to launch the host app page for this Preference. + */ + @Nullable + public PendingIntent getLaunchIntent() { + return mLaunchIntent; + } + + /** + * Returns any additional fields specific to this preference. + * <p>Treat all data as optional. + */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + + /** @hide */ + @IntDef(value = { + NOT_SENSITIVE, + SENSITIVE, + INTENT_ONLY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface WriteSensitivity {} + + /** + * Preference is not sensitive, thus its value is writable without explicit consent, assuming + * all necessary permissions are granted. + */ + public static final int NOT_SENSITIVE = 0; + /** + * Preference is sensitive, meaning that in addition to necessary permissions, writing its value + * will also request explicit user consent. + */ + public static final int SENSITIVE = 1; + /** + * Preference is not permitted for write-access via API and must be changed via Settings page. + */ + public static final int INTENT_ONLY = 2; + + private SettingsPreferenceMetadata(@NonNull Builder builder) { + mKey = builder.mKey; + mScreenKey = builder.mScreenKey; + mTitle = builder.mTitle; + mSummary = builder.mSummary; + mBreadcrumbs = builder.mBreadcrumbs; + mReadPermissions = builder.mReadPermissions; + mWritePermissions = builder.mWritePermissions; + mEnabled = builder.mEnabled; + mAvailable = builder.mAvailable; + mWritable = builder.mWritable; + mRestricted = builder.mRestricted; + mSensitivity = builder.mSensitivity; + mLaunchIntent = builder.mLaunchIntent; + mExtras = Objects.requireNonNullElseGet(builder.mExtras, Bundle::new); + } + @SuppressLint("ParcelClassLoader") + private SettingsPreferenceMetadata(@NonNull Parcel in) { + mKey = Objects.requireNonNull(in.readString8()); + mScreenKey = Objects.requireNonNull(in.readString8()); + mTitle = in.readString8(); + mSummary = in.readString8(); + mBreadcrumbs = new ArrayList<>(); + in.readStringList(mBreadcrumbs); + mReadPermissions = new ArrayList<>(); + in.readStringList(mReadPermissions); + mWritePermissions = new ArrayList<>(); + in.readStringList(mWritePermissions); + mEnabled = in.readBoolean(); + mAvailable = in.readBoolean(); + mWritable = in.readBoolean(); + mRestricted = in.readBoolean(); + mSensitivity = in.readInt(); + mLaunchIntent = in.readParcelable(PendingIntent.class.getClassLoader(), + PendingIntent.class); + mExtras = Objects.requireNonNullElseGet(in.readBundle(), Bundle::new); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mKey); + dest.writeString8(mScreenKey); + dest.writeString8(mTitle); + dest.writeString8(mSummary); + dest.writeStringList(mBreadcrumbs); + dest.writeStringList(mReadPermissions); + dest.writeStringList(mWritePermissions); + dest.writeBoolean(mEnabled); + dest.writeBoolean(mAvailable); + dest.writeBoolean(mWritable); + dest.writeBoolean(mRestricted); + dest.writeInt(mSensitivity); + dest.writeParcelable(mLaunchIntent, flags); + dest.writeBundle(mExtras); + } + + /** + * Parcelable Creator for {@link SettingsPreferenceMetadata}. + */ + @NonNull + public static final Creator<SettingsPreferenceMetadata> CREATOR = new Creator<>() { + @Override + public SettingsPreferenceMetadata createFromParcel(@NonNull Parcel in) { + return new SettingsPreferenceMetadata(in); + } + + @Override + public SettingsPreferenceMetadata[] newArray(int size) { + return new SettingsPreferenceMetadata[size]; + } + }; + + /** + * Builder to construct {@link SettingsPreferenceMetadata}. + */ + public static final class Builder { + private final String mScreenKey; + private final String mKey; + private String mTitle; + private String mSummary; + private List<String> mBreadcrumbs = Collections.emptyList(); + private List<String> mReadPermissions = Collections.emptyList(); + private List<String> mWritePermissions = Collections.emptyList(); + private boolean mEnabled = false; + private boolean mAvailable = false; + private boolean mWritable = false; + private boolean mRestricted = false; + @WriteSensitivity private int mSensitivity = INTENT_ONLY; + private PendingIntent mLaunchIntent; + private Bundle mExtras; + + /** + * Create Builder instance. + * @param screenKey required to be not empty + * @param key required to be not empty + */ + public Builder(@NonNull String screenKey, @NonNull String key) { + if (TextUtils.isEmpty(screenKey)) { + throw new IllegalArgumentException("screenKey cannot be empty"); + } + if (TextUtils.isEmpty(key)) { + throw new IllegalArgumentException("key cannot be empty"); + } + mScreenKey = screenKey; + mKey = key; + } + + /** + * Sets the preference title. + */ + @NonNull + public Builder setTitle(@Nullable String title) { + mTitle = title; + return this; + } + + /** + * Sets the preference summary. + */ + @NonNull + public Builder setSummary(@Nullable String summary) { + mSummary = summary; + return this; + } + + /** + * Sets the preference breadcrumbs (navigation context). + */ + @NonNull + public Builder setBreadcrumbs(@NonNull List<String> breadcrumbs) { + mBreadcrumbs = breadcrumbs; + return this; + } + + /** + * Sets the permissions required for reading this preference. + */ + @NonNull + public Builder setReadPermissions(@NonNull List<String> readPermissions) { + mReadPermissions = readPermissions; + return this; + } + + /** + * Sets the permissions required for writing this preference. + */ + @NonNull + public Builder setWritePermissions(@NonNull List<String> writePermissions) { + mWritePermissions = writePermissions; + return this; + } + + /** + * Set whether the preference is enabled. + */ + @NonNull + public Builder setEnabled(boolean enabled) { + mEnabled = enabled; + return this; + } + + /** + * Sets whether the preference is available. + */ + @NonNull + public Builder setAvailable(boolean available) { + mAvailable = available; + return this; + } + + /** + * Sets whether the preference is writable. + */ + @NonNull + public Builder setWritable(boolean writable) { + mWritable = writable; + return this; + } + + /** + * Sets whether the preference is restricted. + */ + @NonNull + public Builder setRestricted(boolean restricted) { + mRestricted = restricted; + return this; + } + + /** + * Sets the preference write-level sensitivity. + */ + @NonNull + public Builder setWriteSensitivity(@WriteSensitivity int sensitivity) { + mSensitivity = sensitivity; + return this; + } + + /** + * Sets the intent to launch the host app page for this preference. + */ + @NonNull + public Builder setLaunchIntent(@Nullable PendingIntent launchIntent) { + mLaunchIntent = launchIntent; + return this; + } + + /** + * Sets additional fields specific to this preference. Treat all data as optional. + */ + @NonNull + public Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + + /** + * Constructs an immutable {@link SettingsPreferenceMetadata} object. + */ + @NonNull + public SettingsPreferenceMetadata build() { + return new SettingsPreferenceMetadata(this); + } + } +} diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java new file mode 100644 index 000000000000..f056e34a0dd2 --- /dev/null +++ b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java @@ -0,0 +1,220 @@ +/* + * 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.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.SuppressLint; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settingslib.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This objects represents a value that can be used for a particular settings preference. + * <p>The data type for the value will correspond to {@link #getType}. For possible types, see + * constants below, such as {@link #TYPE_BOOLEAN} and {@link #TYPE_STRING}. + * Depending on the type, the corresponding getter will contain its value. All other getters will + * return default values (boolean returns false, String returns null) so they should not be used. + * <p>See documentation on the constants for which getter method should be used. + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class SettingsPreferenceValue implements Parcelable { + + @Type + private final int mType; + private final boolean mBooleanValue; + private final long mLongValue; + private final double mDoubleValue; + @Nullable + private final String mStringValue; + + /** + * Returns the type indicator for Preference value. + */ + @Type + public int getType() { + return mType; + } + + /** + * Returns the boolean value for Preference if type is {@link #TYPE_BOOLEAN}. + */ + public boolean getBooleanValue() { + return mBooleanValue; + } + + /** + * Returns the long value for Preference if type is {@link #TYPE_LONG}. + */ + public long getLongValue() { + return mLongValue; + } + + /** + * Returns the double value for Preference if type is {@link #TYPE_DOUBLE}. + */ + public double getDoubleValue() { + return mDoubleValue; + } + + /** + * Returns the string value for Preference if type is {@link #TYPE_STRING}. + */ + @Nullable + public String getStringValue() { + return mStringValue; + } + + /** @hide */ + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_BOOLEAN, + TYPE_LONG, + TYPE_DOUBLE, + TYPE_STRING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + /** Value is of type boolean. Access via {@link #getBooleanValue}. */ + public static final int TYPE_BOOLEAN = 0; + /** Value is of type long. Access via {@link #getLongValue()}. */ + public static final int TYPE_LONG = 1; + /** Value is of type double. Access via {@link #getDoubleValue()}. */ + public static final int TYPE_DOUBLE = 2; + /** Value is of type string. Access via {@link #getStringValue}. */ + public static final int TYPE_STRING = 3; + + private SettingsPreferenceValue(@NonNull Builder builder) { + mType = builder.mType; + mBooleanValue = builder.mBooleanValue; + mLongValue = builder.mLongValue; + mDoubleValue = builder.mDoubleValue; + mStringValue = builder.mStringValue; + } + + private SettingsPreferenceValue(@NonNull Parcel in) { + mType = in.readInt(); + mBooleanValue = in.readBoolean(); + mLongValue = in.readLong(); + mDoubleValue = in.readDouble(); + mStringValue = in.readString8(); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeBoolean(mBooleanValue); + dest.writeLong(mLongValue); + dest.writeDouble(mDoubleValue); + dest.writeString8(mStringValue); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link SettingsPreferenceValue}. + */ + @NonNull + public static final Creator<SettingsPreferenceValue> CREATOR = new Creator<>() { + @Override + public SettingsPreferenceValue createFromParcel(@NonNull Parcel in) { + return new SettingsPreferenceValue(in); + } + + @Override + public SettingsPreferenceValue[] newArray(int size) { + return new SettingsPreferenceValue[size]; + } + }; + + /** + * Builder to construct {@link SettingsPreferenceValue}. + */ + public static final class Builder { + @Type + private final int mType; + private boolean mBooleanValue; + private long mLongValue; + private double mDoubleValue; + private String mStringValue; + + /** + * Create Builder instance. + * @param type type indicator for preference value + */ + public Builder(@Type int type) { + mType = type; + } + + /** + * Sets boolean value for Preference. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public Builder setBooleanValue(boolean booleanValue) { + mBooleanValue = booleanValue; + return this; + } + + /** + * Sets long value for Preference. + */ + @NonNull + public Builder setLongValue(long longValue) { + mLongValue = longValue; + return this; + } + + /** + * Sets floating point value for Preference. + */ + @NonNull + public Builder setDoubleValue(double doubleValue) { + mDoubleValue = doubleValue; + return this; + } + + /** + * Sets string value for Preference. + */ + @NonNull + public Builder setStringValue(@Nullable String stringValue) { + mStringValue = stringValue; + return this; + } + + /** + * Constructs an immutable {@link SettingsPreferenceValue} object. + */ + @NonNull + public SettingsPreferenceValue build() { + return new SettingsPreferenceValue(this); + } + } +} diff --git a/core/java/android/service/wallpaper/IWallpaperService.aidl b/core/java/android/service/wallpaper/IWallpaperService.aidl index f76e6cee13f0..bcdd4775c164 100644 --- a/core/java/android/service/wallpaper/IWallpaperService.aidl +++ b/core/java/android/service/wallpaper/IWallpaperService.aidl @@ -28,6 +28,6 @@ oneway interface IWallpaperService { void attach(IWallpaperConnection connection, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, in Rect padding, int displayId, int which, - in WallpaperInfo info, in @nullable WallpaperDescription description); + in WallpaperInfo info, in WallpaperDescription description); void detach(IBinder windowToken); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 131fdc895841..2061abac248e 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -17,6 +17,7 @@ package android.service.wallpaper; import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING; +import static android.app.Flags.liveWallpaperContentHandling; import static android.app.WallpaperManager.COMMAND_FREEZE; import static android.app.WallpaperManager.COMMAND_UNFREEZE; import static android.app.WallpaperManager.SetWallpaperFlags; @@ -2624,7 +2625,7 @@ public abstract class WallpaperService extends Service { private void doAttachEngine() { Trace.beginSection("WPMS.onCreateEngine"); Engine engine; - if (mDescription != null) { + if (liveWallpaperContentHandling()) { engine = onCreateEngine(mDescription); } else { engine = onCreateEngine(); diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 910e644f7b6f..0241e9437950 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1549,8 +1549,9 @@ public final class Display { // Although we only care about the HDR/SDR ratio changing, that can also come in the // form of the larger DISPLAY_CHANGED event mGlobal.registerDisplayListener(toRegister, executor, - DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED - | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED, + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | DisplayManagerGlobal + .INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED, ActivityThread.currentPackageName()); } diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 8f112f338a00..4ff04d5c1fa6 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -447,7 +447,6 @@ public final class DisplayInfo implements Parcelable { && Objects.equals(displayCutout, other.displayCutout) && rotation == other.rotation && modeId == other.modeId - && renderFrameRate == other.renderFrameRate && hasArrSupport == other.hasArrSupport && Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate) && defaultModeId == other.defaultModeId @@ -705,6 +704,9 @@ public final class DisplayInfo implements Parcelable { if (refreshRateOverride > 0) { return refreshRateOverride; } + if (renderFrameRate > 0) { + return renderFrameRate; + } if (supportedModes.length == 0) { return 0; } diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index dddc408ed9db..38e4e2760d25 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -935,7 +935,6 @@ public class KeyEvent extends InputEvent implements Parcelable { */ public static final int KEYCODE_MACRO_4 = 316; /** Key code constant: To open emoji picker */ - @FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE) public static final int KEYCODE_EMOJI_PICKER = 317; /** * Key code constant: To take a screenshot @@ -944,15 +943,80 @@ public class KeyEvent extends InputEvent implements Parcelable { * unlike {@code KEYCODE_SYSRQ} which is sent to the app first and only if the app * doesn't handle it, the framework handles it (to take a screenshot). */ - @FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE) public static final int KEYCODE_SCREENSHOT = 318; + /** Key code constant: To start dictate to an input field */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_DICTATE = 319; + /** + * Key code constant: AC New + * + * e.g. To create a new instance of a window, open a new tab, etc. + */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_NEW = 320; + /** + * Key code constant: AC Close + * + * e.g. To close current instance of the application window, close the current tab, etc. + */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_CLOSE = 321; + /** Key code constant: To toggle 'Do Not Disturb' mode */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_DO_NOT_DISTURB = 322; + /** Key code constant: To Print */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_PRINT = 323; + /** Key code constant: To Lock the screen */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_LOCK = 324; + /** Key code constant: To toggle fullscreen mode (on the current application) */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_FULLSCREEN = 325; + /** Key code constant: F13 key. */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_F13 = 326; + /** Key code constant: F14 key. */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_F14 = 327; + /** Key code constant: F15 key. */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_F15 = 328; + /** Key code constant: F16 key. */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_F16 = 329; + /** Key code constant: F17 key. */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_F17 = 330; + /** Key code constant: F18 key. */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_F18 = 331; + /** Key code constant: F19 key. */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_F19 = 332; + /** Key code constant: F20 key. */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_F20 = 333; + /** Key code constant: F21 key. */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_F21 = 334; + /** Key code constant: F22 key. */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_F22 = 335; + /** Key code constant: F23 key. */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_F23 = 336; + /** Key code constant: F24 key. */ + @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + public static final int KEYCODE_F24 = 337; /** * Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent. * @hide */ @TestApi - public static final int LAST_KEYCODE = KEYCODE_SCREENSHOT; + @SuppressWarnings("FlaggedApi") + public static final int LAST_KEYCODE = KEYCODE_F24; /** @hide */ @IntDef(prefix = {"KEYCODE_"}, value = { @@ -1275,6 +1339,25 @@ public class KeyEvent extends InputEvent implements Parcelable { KEYCODE_MACRO_4, KEYCODE_EMOJI_PICKER, KEYCODE_SCREENSHOT, + KEYCODE_DICTATE, + KEYCODE_NEW, + KEYCODE_CLOSE, + KEYCODE_DO_NOT_DISTURB, + KEYCODE_PRINT, + KEYCODE_LOCK, + KEYCODE_FULLSCREEN, + KEYCODE_F13, + KEYCODE_F14, + KEYCODE_F15, + KEYCODE_F16, + KEYCODE_F17, + KEYCODE_F18, + KEYCODE_F19, + KEYCODE_F20, + KEYCODE_F21, + KEYCODE_F22, + KEYCODE_F23, + KEYCODE_F24, }) @Retention(RetentionPolicy.SOURCE) @interface KeyCode {} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 618843ccb005..fa06831f6514 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -23882,12 +23882,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { draw(canvas); } - } - // For VRR to vote the preferred frame rate - if (sToolkitSetFrameRateReadOnlyFlagValue - && sToolkitFrameRateViewEnablingReadOnlyFlagValue) { - votePreferredFrameRate(); + // For VRR to vote the preferred frame rate + if (sToolkitSetFrameRateReadOnlyFlagValue + && sToolkitFrameRateViewEnablingReadOnlyFlagValue) { + votePreferredFrameRate(); + } } } finally { renderNode.endRecording(); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3ce6870bf2ca..9a2aa0b8a682 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -185,7 +185,6 @@ import android.graphics.RenderNode; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.hardware.SyncFence; -import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.display.DisplayManagerGlobal; import android.hardware.input.InputManagerGlobal; @@ -1816,9 +1815,9 @@ public final class ViewRootImpl implements ViewParent, .registerDisplayListener( mDisplayListener, mHandler, - DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, mBasePackageName); if (forceInvertColor()) { diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java index a5be58b7b183..16eb43700aef 100644 --- a/core/java/android/window/BackProgressAnimator.java +++ b/core/java/android/window/BackProgressAnimator.java @@ -16,8 +16,11 @@ package android.window; +import static android.window.BackEvent.EDGE_NONE; + import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.window.flags.Flags.predictiveBackTimestampApi; +import static com.android.window.flags.Flags.predictiveBackSwipeEdgeNoneApi; import android.annotation.NonNull; import android.annotation.Nullable; @@ -60,6 +63,12 @@ public class BackProgressAnimator implements DynamicAnimation.OnAnimationUpdateL @Nullable private Runnable mBackInvokedFinishRunnable; private FlingAnimation mBackInvokedFlingAnim; + private final SpringForce mGestureSpringForce = new SpringForce() + .setStiffness(SpringForce.STIFFNESS_MEDIUM) + .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY); + private final SpringForce mButtonSpringForce = new SpringForce() + .setStiffness(500) + .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY); private final DynamicAnimation.OnAnimationEndListener mOnAnimationEndListener = (animation, canceled, value, velocity) -> { if (mBackCancelledFinishRunnable != null) invokeBackCancelledRunnable(); @@ -109,9 +118,7 @@ public class BackProgressAnimator implements DynamicAnimation.OnAnimationUpdateL public BackProgressAnimator() { mSpring = new SpringAnimation(this, PROGRESS_PROP); mSpring.addUpdateListener(this); - mSpring.setSpring(new SpringForce() - .setStiffness(SpringForce.STIFFNESS_MEDIUM) - .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)); + mSpring.setSpring(mGestureSpringForce); } /** @@ -123,6 +130,11 @@ public class BackProgressAnimator implements DynamicAnimation.OnAnimationUpdateL if (!mBackAnimationInProgress) { return; } + if (predictiveBackSwipeEdgeNoneApi()) { + if (event.getSwipeEdge() == EDGE_NONE) { + return; + } + } mLastBackEvent = event; if (mSpring == null) { return; @@ -143,7 +155,17 @@ public class BackProgressAnimator implements DynamicAnimation.OnAnimationUpdateL mBackAnimationInProgress = true; updateProgressValue(/* progress */ 0, /* velocity */ 0, /* frameTime */ System.nanoTime() / TimeUtils.NANOS_PER_MS); - onBackProgressed(event); + if (predictiveBackSwipeEdgeNoneApi()) { + if (event.getSwipeEdge() == EDGE_NONE) { + mSpring.setSpring(mButtonSpringForce); + mSpring.animateToFinalPosition(SCALE_FACTOR); + } else { + mSpring.setSpring(mGestureSpringForce); + onBackProgressed(event); + } + } else { + onBackProgressed(event); + } } /** diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 11f68496fa90..68e78fed29c5 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -76,6 +76,14 @@ flag { } flag { + name: "disable_opt_out_edge_to_edge" + namespace: "windowing_frontend" + description: "Deprecate and disable windowOptOutEdgeToEdgeEnforcement" + bug: "377864165" + is_fixed_read_only: true +} + +flag { name: "keyguard_going_away_timeout" namespace: "windowing_frontend" description: "Allow a maximum of 10 seconds with keyguardGoingAway=true before force-resetting" @@ -388,4 +396,11 @@ flag { description: "Provide pre-make predictive back API extension" is_fixed_read_only: true bug: "362938401" -}
\ No newline at end of file +} + +flag { + name: "predictive_back_three_button_nav" + namespace: "systemui" + description: "Enable Predictive Back Animation for 3-button-nav" + bug: "373544911" +} diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java index 21fbf9d03c71..a50dbb0223b7 100644 --- a/core/java/com/android/internal/display/BrightnessSynchronizer.java +++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java @@ -600,8 +600,8 @@ public class BrightnessSynchronizer { final ContentResolver cr = mContext.getContentResolver(); cr.registerContentObserver(BRIGHTNESS_URI, false, createBrightnessContentObserver(handler), UserHandle.USER_ALL); - mDisplayManager.registerDisplayListener(mListener, handler, - DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS); + mDisplayManager.registerDisplayListener(mListener, handler, /* eventFlags */ 0, + DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS); mIsObserving = true; } } diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java index ca6c54dc0285..0c2fd4bbd7ae 100644 --- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java +++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java @@ -147,8 +147,9 @@ public class DisplayResolutionTracker { @Override public void registerDisplayListener(DisplayManager.DisplayListener listener) { manager.registerDisplayListener(listener, handler, - DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED + | DisplayManagerGlobal + .INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, ActivityThread.currentPackageName()); } diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index b3480ab92f46..2931bd2c83dd 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -56,7 +56,7 @@ import libcore.util.NativeAllocationRegistry; * @hide */ @RavenwoodKeepWholeClass -@RavenwoodRedirectionClass("LongArrayMultiStateCounter_host") +@RavenwoodRedirectionClass("LongArrayMultiStateCounter_ravenwood") public final class LongArrayMultiStateCounter implements Parcelable { private static volatile NativeAllocationRegistry sRegistry; private final int mStateCount; diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java index 90608f6e87d1..7030d8e84b70 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java @@ -18,6 +18,7 @@ package com.android.internal.os; import android.os.BadParcelableException; import android.os.Parcel; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import java.util.Arrays; import java.util.HashMap; @@ -25,7 +26,8 @@ import java.util.HashMap; /** * Native implementation substitutions for the LongArrayMultiStateCounter class. */ -public class LongArrayMultiStateCounter_host { +@RavenwoodKeepWholeClass +class LongArrayMultiStateCounter_ravenwood { /** * A reimplementation of {@link LongArrayMultiStateCounter}, only in diff --git a/core/java/com/android/internal/os/LongMultiStateCounter.java b/core/java/com/android/internal/os/LongMultiStateCounter.java index c386a86f5906..ee855d58c874 100644 --- a/core/java/com/android/internal/os/LongMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongMultiStateCounter.java @@ -60,7 +60,7 @@ import libcore.util.NativeAllocationRegistry; * @hide */ @RavenwoodKeepWholeClass -@RavenwoodRedirectionClass("LongMultiStateCounter_host") +@RavenwoodRedirectionClass("LongMultiStateCounter_ravenwood") public final class LongMultiStateCounter implements Parcelable { private static NativeAllocationRegistry sRegistry; diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongMultiStateCounter_host.java b/core/java/com/android/internal/os/LongMultiStateCounter_ravenwood.java index 1d95aa143549..42db37f1f82f 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongMultiStateCounter_host.java +++ b/core/java/com/android/internal/os/LongMultiStateCounter_ravenwood.java @@ -18,13 +18,15 @@ package com.android.internal.os; import android.os.BadParcelableException; import android.os.Parcel; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import java.util.HashMap; /** * Native implementation substitutions for the LongMultiStateCounter class. */ -public class LongMultiStateCounter_host { +@RavenwoodKeepWholeClass +class LongMultiStateCounter_ravenwood { /** * A reimplementation of {@link com.android.internal.os.LongMultiStateCounter}, only in diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java index 1938cdb0ba84..40161023eae4 100644 --- a/core/java/com/android/internal/statusbar/StatusBarIcon.java +++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java @@ -40,9 +40,6 @@ public class StatusBarIcon implements Parcelable { public enum Type { // Notification: the sender avatar for important conversations PeopleAvatar, - // Notification: the monochrome version of the app icon if available; otherwise fall back to - // the small icon - MaybeMonochromeAppIcon, // Notification: the small icon from the notification NotifSmallIcon, // The wi-fi, cellular or battery icon. diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java index adcc0f64b598..5fc61b00e331 100644 --- a/core/java/com/android/internal/widget/NotificationRowIconView.java +++ b/core/java/com/android/internal/widget/NotificationRowIconView.java @@ -22,11 +22,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -35,8 +31,6 @@ import android.util.AttributeSet; import android.view.RemotableViewMethod; import android.widget.RemoteViews; -import com.android.internal.R; - /** * An image view that holds the icon displayed at the start of a notification row. * This can generally either display the "small icon" of a notification set via @@ -48,7 +42,6 @@ public class NotificationRowIconView extends CachingIconView { private NotificationIconProvider mIconProvider; private boolean mApplyCircularCrop = false; - private boolean mShouldShowAppIcon = false; private Drawable mAppIcon = null; // Padding, background and colors set on the view prior to being overridden when showing the app @@ -77,17 +70,6 @@ public class NotificationRowIconView extends CachingIconView { super(context, attrs, defStyleAttr, defStyleRes); } - @Override - protected void onFinishInflate() { - // If showing the app icon, we don't need background or padding. - if (Flags.notificationsUseAppIcon()) { - setPadding(0, 0, 0, 0); - setBackground(null); - } - - super.onFinishInflate(); - } - /** * Sets the icon provider for this view. This is used to determine whether we should show the * app icon instead of the small icon, and to fetch the app icon if needed. @@ -153,37 +135,12 @@ public class NotificationRowIconView extends CachingIconView { return super.setImageIconAsync(icon); } - /** Whether the icon represents the app icon (instead of the small icon). */ - @RemotableViewMethod - public void setShouldShowAppIcon(boolean shouldShowAppIcon) { - if (Flags.notificationsUseAppIconInRow()) { - if (mShouldShowAppIcon == shouldShowAppIcon) { - return; // no change - } - - mShouldShowAppIcon = shouldShowAppIcon; - if (mShouldShowAppIcon) { - adjustViewForAppIcon(); - } else { - // Restore original padding and background if needed - restoreViewForSmallIcon(); - } - } - } - /** * Override padding and background from the view to display the app icon. */ private void adjustViewForAppIcon() { removePadding(); - - if (Flags.notificationsUseAppIconInRow()) { - addWhiteBackground(); - } else { - // No need to set the background for notification redesign, since the icon - // factory already does that for us. - removeBackground(); - } + removeBackground(); } /** @@ -221,21 +178,6 @@ public class NotificationRowIconView extends CachingIconView { setBackground(null); } - private void addWhiteBackground() { - if (mOriginalBackground == null) { - mOriginalBackground = getBackground(); - } - - // Make the background white in case the icon itself doesn't have one. - ColorFilter colorFilter = new PorterDuffColorFilter(Color.WHITE, - PorterDuff.Mode.SRC_ATOP); - - if (mOriginalBackground == null) { - setBackground(getContext().getDrawable(R.drawable.notification_icon_circle)); - } - getBackground().mutate().setColorFilter(colorFilter); - } - private void restoreBackground() { // NOTE: This will not work if the original background was null, but that's better than // accidentally clearing the background. We expect that there's generally going to be one 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 212df02b1bd3..0761a244b9f2 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java @@ -15,12 +15,14 @@ */ package com.android.internal.widget.remotecompose.core; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.operations.ComponentValue; import com.android.internal.widget.remotecompose.core.operations.IntegerExpression; import com.android.internal.widget.remotecompose.core.operations.NamedVariable; import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior; import com.android.internal.widget.remotecompose.core.operations.Theme; -import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierEnd; import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd; @@ -28,7 +30,11 @@ import com.android.internal.widget.remotecompose.core.operations.layout.Componen import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent; import com.android.internal.widget.remotecompose.core.operations.layout.LoopEnd; import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.OperationsListEnd; import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent; +import com.android.internal.widget.remotecompose.core.operations.layout.TouchCancelModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.TouchDownModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.TouchUpModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation; import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; @@ -49,12 +55,12 @@ public class CoreDocument { ArrayList<Operation> mOperations; - RootLayoutComponent mRootLayoutComponent = null; + @Nullable RootLayoutComponent mRootLayoutComponent = null; RemoteComposeState mRemoteComposeState = new RemoteComposeState(); - TimeVariables mTimeVariables = new TimeVariables(); + @NonNull TimeVariables mTimeVariables = new TimeVariables(); // Semantic version of the document - Version mVersion = new Version(0, 1, 0); + @NonNull Version mVersion = new Version(0, 1, 0); String mContentDescription; // text description of the document (used for accessibility) @@ -72,6 +78,8 @@ public class CoreDocument { private final HashMap<Long, IntegerExpression> mIntegerExpressions = new HashMap<>(); + private HashSet<Component> mAppliedTouchOperations = new HashSet<>(); + private int mLastId = 1; // last component id when inflating the file public String getContentDescription() { @@ -272,6 +280,7 @@ public class CoreDocument { * * @return list of click areas in document coordinates */ + @NonNull public Set<ClickAreaRepresentation> getClickAreas() { return mClickAreas; } @@ -281,6 +290,7 @@ public class CoreDocument { * * @return returns the root component if it exists, null otherwise */ + @Nullable public RootLayoutComponent getRootLayoutComponent() { return mRootLayoutComponent; } @@ -298,6 +308,7 @@ public class CoreDocument { * @param id component id * @return the component if it exists, null otherwise */ + @Nullable public Component getComponent(int id) { if (mRootLayoutComponent != null) { return mRootLayoutComponent.getComponent(id); @@ -310,6 +321,7 @@ public class CoreDocument { * * @return a standardized string representation of the component hierarchy */ + @NonNull public String displayHierarchy() { StringSerializer serializer = new StringSerializer(); for (Operation op : mOperations) { @@ -329,7 +341,8 @@ public class CoreDocument { * @param targetId the id of the value to update with the expression * @param context the current context */ - public void evaluateIntExpression(long expressionId, int targetId, RemoteContext context) { + public void evaluateIntExpression( + long expressionId, int targetId, @NonNull RemoteContext context) { IntegerExpression expression = mIntegerExpressions.get(expressionId); if (expression != null) { int v = expression.evaluate(context); @@ -337,22 +350,46 @@ public class CoreDocument { } } + // ============== Haptic support ================== + public interface HapticEngine { + void haptic(int type); + } + + HapticEngine mHapticEngine; + + public void setHapticEngine(HapticEngine engine) { + mHapticEngine = engine; + } + + public void haptic(int type) { + if (mHapticEngine != null) { + mHapticEngine.haptic(type); + } + } + + // ============== Haptic support ================== + + public void appliedTouchOperation(Component operation) { + mAppliedTouchOperations.add(operation); + } + /** Callback interface for host actions */ public interface ActionCallback { - // TODO: add payload support - void onAction(String name); + void onAction(String name, Object value); } - HashSet<ActionCallback> mActionListeners = new HashSet<ActionCallback>(); + @NonNull HashSet<ActionCallback> mActionListeners = new HashSet<ActionCallback>(); /** * Warn action listeners for the given named action * * @param name the action name + * @param value a parameter to the action */ - public void runNamedAction(String name) { + public void runNamedAction(String name, Object value) { + // TODO: we might add an interface to group all valid parameter types for (ActionCallback callback : mActionListeners) { - callback.onAction(name); + callback.onAction(name, value); } } @@ -374,8 +411,9 @@ public class CoreDocument { void click(int id, String metadata); } - HashSet<ClickCallbacks> mClickListeners = new HashSet<>(); - HashSet<ClickAreaRepresentation> mClickAreas = new HashSet<>(); + @NonNull HashSet<ClickCallbacks> mClickListeners = new HashSet<>(); + @NonNull HashSet<TouchListener> mTouchListeners = new HashSet<>(); + @NonNull HashSet<ClickAreaRepresentation> mClickAreas = new HashSet<>(); static class Version { public final int major; @@ -456,7 +494,7 @@ public class CoreDocument { } /** Load operations from the given buffer */ - public void initFromBuffer(RemoteComposeBuffer buffer) { + public void initFromBuffer(@NonNull RemoteComposeBuffer buffer) { mOperations = new ArrayList<Operation>(); buffer.inflateFromBuffer(mOperations); for (Operation op : mOperations) { @@ -484,12 +522,16 @@ public class CoreDocument { * @param operations flat list of operations * @return nested list of operations / components */ - private ArrayList<Operation> inflateComponents(ArrayList<Operation> operations) { + @NonNull + private ArrayList<Operation> inflateComponents(@NonNull ArrayList<Operation> operations) { Component currentComponent = null; ArrayList<Component> components = new ArrayList<>(); ArrayList<Operation> finalOperationsList = new ArrayList<>(); ArrayList<Operation> ops = finalOperationsList; ClickModifierOperation currentClickModifier = null; + TouchDownModifierOperation currentTouchDownModifier = null; + TouchUpModifierOperation currentTouchUpModifier = null; + TouchCancelModifierOperation currentTouchCancelModifier = null; LoopOperation currentLoop = null; mLastId = -1; @@ -519,10 +561,30 @@ public class CoreDocument { // TODO: refactor to add container <- component... currentClickModifier = (ClickModifierOperation) o; ops = currentClickModifier.getList(); - } else if (o instanceof ClickModifierEnd) { + } else if (o instanceof TouchDownModifierOperation) { + currentTouchDownModifier = (TouchDownModifierOperation) o; + ops = currentTouchDownModifier.getList(); + } else if (o instanceof TouchUpModifierOperation) { + currentTouchUpModifier = (TouchUpModifierOperation) o; + ops = currentTouchUpModifier.getList(); + } else if (o instanceof TouchCancelModifierOperation) { + currentTouchCancelModifier = (TouchCancelModifierOperation) o; + ops = currentTouchCancelModifier.getList(); + } else if (o instanceof OperationsListEnd) { ops = currentComponent.getList(); - ops.add(currentClickModifier); - currentClickModifier = null; + if (currentClickModifier != null) { + ops.add(currentClickModifier); + currentClickModifier = null; + } else if (currentTouchDownModifier != null) { + ops.add(currentTouchDownModifier); + currentTouchDownModifier = null; + } else if (currentTouchUpModifier != null) { + ops.add(currentTouchUpModifier); + currentTouchUpModifier = null; + } else if (currentTouchCancelModifier != null) { + ops.add(currentTouchCancelModifier); + currentTouchCancelModifier = null; + } } else if (o instanceof LoopOperation) { currentLoop = (LoopOperation) o; ops = currentLoop.getList(); @@ -541,9 +603,9 @@ public class CoreDocument { return ops; } - private HashMap<Integer, Component> mComponentMap = new HashMap<Integer, Component>(); + @NonNull private HashMap<Integer, Component> mComponentMap = new HashMap<Integer, Component>(); - private void registerVariables(RemoteContext context, ArrayList<Operation> list) { + private void registerVariables(RemoteContext context, @NonNull ArrayList<Operation> list) { for (Operation op : list) { if (op instanceof VariableSupport) { ((VariableSupport) op).updateVariables(context); @@ -578,7 +640,7 @@ public class CoreDocument { * Called when an initialization is needed, allowing the document to eg load resources / cache * them. */ - public void initializeContext(RemoteContext context) { + public void initializeContext(@NonNull RemoteContext context) { mRemoteComposeState.reset(); mRemoteComposeState.setContext(context); mClickAreas.clear(); @@ -651,6 +713,15 @@ public class CoreDocument { } /** + * Called by commands to listen to touch events + * + * @param listener + */ + public void addTouchListener(TouchListener listener) { + mTouchListeners.add(listener); + } + + /** * Add a click listener. This will get called when a click is detected on the document * * @param callback called when a click area has been hit, passing the click are id and metadata. @@ -664,6 +735,7 @@ public class CoreDocument { * * @return set of click listeners */ + @NonNull public HashSet<CoreDocument.ClickCallbacks> getClickListeners() { return mClickListeners; } @@ -700,12 +772,98 @@ public class CoreDocument { } /** Warn click listeners when a click area is activated */ - private void warnClickListeners(ClickAreaRepresentation clickArea) { + private void warnClickListeners(@NonNull ClickAreaRepresentation clickArea) { for (ClickCallbacks listener : mClickListeners) { listener.click(clickArea.mId, clickArea.mMetadata); } } + /** + * Returns true if the document has touch listeners + * + * @return true if the document needs to react to touch events + */ + public boolean hasTouchListener() { + boolean hasComponentsTouchListeners = + mRootLayoutComponent != null && mRootLayoutComponent.hasTouchListeners(); + return hasComponentsTouchListeners || !mTouchListeners.isEmpty(); + } + + // TODO support velocity estimate support, support regions + /** + * Support touch drag events on commands supporting touch + * + * @param x position of touch + * @param y position of touch + */ + public boolean touchDrag(RemoteContext context, float x, float y) { + context.loadFloat(RemoteContext.ID_TOUCH_POS_X, x); + context.loadFloat(RemoteContext.ID_TOUCH_POS_Y, y); + for (TouchListener clickArea : mTouchListeners) { + clickArea.touchDrag(context, x, y); + } + if (!mTouchListeners.isEmpty()) { + return true; + } + return false; + } + + /** + * Support touch down events on commands supporting touch + * + * @param x position of touch + * @param y position of touch + */ + public void touchDown(RemoteContext context, float x, float y) { + context.loadFloat(RemoteContext.ID_TOUCH_POS_X, x); + context.loadFloat(RemoteContext.ID_TOUCH_POS_Y, y); + for (TouchListener clickArea : mTouchListeners) { + clickArea.touchDown(context, x, y); + } + if (mRootLayoutComponent != null) { + mRootLayoutComponent.onTouchDown(context, this, x, y); + } + mRepaintNext = 1; + } + + /** + * Support touch up events on commands supporting touch + * + * @param x position of touch + * @param y position of touch + */ + public void touchUp(RemoteContext context, float x, float y, float dx, float dy) { + context.loadFloat(RemoteContext.ID_TOUCH_POS_X, x); + context.loadFloat(RemoteContext.ID_TOUCH_POS_Y, y); + for (TouchListener clickArea : mTouchListeners) { + clickArea.touchUp(context, x, y, dx, dy); + } + if (mRootLayoutComponent != null) { + for (Component component : mAppliedTouchOperations) { + component.onTouchUp(context, this, x, y, true); + } + mAppliedTouchOperations.clear(); + } + mRepaintNext = 1; + } + + /** + * Support touch cancel events on commands supporting touch + * + * @param x position of touch + * @param y position of touch + */ + public void touchCancel(RemoteContext context, float x, float y, float dx, float dy) { + if (mRootLayoutComponent != null) { + for (Component component : mAppliedTouchOperations) { + component.onTouchCancel(context, this, x, y, true); + } + mAppliedTouchOperations.clear(); + } + mRepaintNext = 1; + } + + @NonNull @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -721,12 +879,22 @@ public class CoreDocument { * * @return array of named colors or null */ + @Nullable public String[] getNamedColors() { + return getNamedVariables(NamedVariable.COLOR_TYPE); + } + + /** + * Gets the names of all named Variables. + * + * @return array of named variables or null + */ + public String[] getNamedVariables(int type) { int count = 0; for (Operation op : mOperations) { if (op instanceof NamedVariable) { NamedVariable n = (NamedVariable) op; - if (n.mVarType == NamedVariable.COLOR_TYPE) { + if (n.mVarType == type) { count++; } } @@ -739,7 +907,7 @@ public class CoreDocument { for (Operation op : mOperations) { if (op instanceof NamedVariable) { NamedVariable n = (NamedVariable) op; - if (n.mVarType == NamedVariable.COLOR_TYPE) { + if (n.mVarType == type) { ret[i++] = n.mVarName; } } @@ -770,10 +938,9 @@ public class CoreDocument { * @param context the provided PaintContext * @param theme the theme we want to use for this document. */ - public void paint(RemoteContext context, int theme) { + public void paint(@NonNull RemoteContext context, int theme) { context.getPaintContext().clearNeedsRepaint(); context.mMode = RemoteContext.ContextMode.UNSET; - // current theme starts as UNSPECIFIED, until a Theme setter // operation gets executed and modify it. context.setTheme(Theme.UNSPECIFIED); @@ -807,6 +974,7 @@ public class CoreDocument { } // TODO -- this should be specifically about applying animation, not paint mRootLayoutComponent.paint(context.getPaintContext()); + context.mPaintContext.reset(); // TODO -- should be able to remove this mRootLayoutComponent.updateVariables(context); if (DEBUG) { @@ -843,6 +1011,7 @@ public class CoreDocument { } } + @NonNull public String[] getStats() { ArrayList<String> ret = new ArrayList<>(); WireBuffer buffer = new WireBuffer(); @@ -875,7 +1044,7 @@ public class CoreDocument { return ret.toArray(new String[0]); } - private int sizeOfComponent(Operation com, WireBuffer tmp) { + private int sizeOfComponent(@NonNull Operation com, @NonNull WireBuffer tmp) { tmp.reset(100); com.write(tmp); int size = tmp.getSize(); @@ -883,7 +1052,8 @@ public class CoreDocument { return size; } - private int addChildren(Component base, HashMap<String, int[]> map, WireBuffer tmp) { + private int addChildren( + @NonNull Component base, @NonNull HashMap<String, int[]> map, @NonNull WireBuffer tmp) { int count = base.mList.size(); for (Operation mOperation : base.mList) { Class<? extends Operation> c = mOperation.getClass(); @@ -903,6 +1073,7 @@ public class CoreDocument { return count; } + @NonNull public String toNestedString() { StringBuilder ret = new StringBuilder(); for (Operation mOperation : mOperations) { @@ -915,7 +1086,8 @@ public class CoreDocument { return ret.toString(); } - private void toNestedString(Component base, StringBuilder ret, String indent) { + private void toNestedString( + @NonNull Component base, @NonNull StringBuilder ret, String indent) { for (Operation mOperation : base.mList) { ret.append(mOperation.toString()); ret.append("\n"); diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operation.java b/core/java/com/android/internal/widget/remotecompose/core/Operation.java index 9f565a2915fb..f1885f942ee1 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operation.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core; +import android.annotation.Nullable; + /** Base interface for RemoteCompose operations */ public interface Operation { @@ -29,5 +31,6 @@ public interface Operation { void apply(RemoteContext context); /** Debug utility to display an operation + indentation */ + @Nullable String deepToString(String indent); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java index acebe0761b79..53c45fac826c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.operations.BitmapData; import com.android.internal.widget.remotecompose.core.operations.ClickArea; import com.android.internal.widget.remotecompose.core.operations.ClipPath; @@ -65,15 +67,19 @@ import com.android.internal.widget.remotecompose.core.operations.TextLookupInt; import com.android.internal.widget.remotecompose.core.operations.TextMeasure; import com.android.internal.widget.remotecompose.core.operations.TextMerge; import com.android.internal.widget.remotecompose.core.operations.Theme; +import com.android.internal.widget.remotecompose.core.operations.TouchExpression; import com.android.internal.widget.remotecompose.core.operations.layout.CanvasContent; -import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierEnd; import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd; import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStart; import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponentContent; import com.android.internal.widget.remotecompose.core.operations.layout.LoopEnd; import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.OperationsListEnd; import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent; +import com.android.internal.widget.remotecompose.core.operations.layout.TouchCancelModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.TouchDownModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.TouchUpModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec; import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout; @@ -85,15 +91,19 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BorderModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostNamedActionOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.OffsetModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueFloatChangeActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerChangeActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerExpressionChangeActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueStringChangeActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation; import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap; import com.android.internal.widget.remotecompose.core.types.BooleanConstant; import com.android.internal.widget.remotecompose.core.types.IntegerConstant; @@ -165,6 +175,7 @@ public class Operations { public static final int DATA_MAP_LOOKUP = 154; public static final int TEXT_MEASURE = 155; public static final int TEXT_LENGTH = 156; + public static final int TOUCH_EXPRESSION = 157; ///////////////////////////////////////// ====================== @@ -194,8 +205,16 @@ public class Operations { public static final int MODIFIER_ROUNDED_CLIP_RECT = 54; public static final int MODIFIER_CLICK = 59; + public static final int MODIFIER_TOUCH_DOWN = 219; + public static final int MODIFIER_TOUCH_UP = 220; + public static final int MODIFIER_TOUCH_CANCEL = 225; + + public static final int OPERATIONS_LIST_END = 214; + + public static final int MODIFIER_OFFSET = 221; + public static final int MODIFIER_ZINDEX = 223; + public static final int MODIFIER_GRAPHICS_LAYER = 224; - public static final int MODIFIER_CLICK_END = 214; public static final int LOOP_START = 215; public static final int LOOP_END = 216; @@ -206,12 +225,13 @@ public class Operations { public static final int VALUE_INTEGER_CHANGE_ACTION = 212; public static final int VALUE_STRING_CHANGE_ACTION = 213; public static final int VALUE_INTEGER_EXPRESSION_CHANGE_ACTION = 218; + public static final int VALUE_FLOAT_CHANGE_ACTION = 222; public static final int ANIMATION_SPEC = 14; public static final int COMPONENT_VALUE = 150; - public static UniqueIntMap<CompanionOperation> map = new UniqueIntMap<>(); + @NonNull public static UniqueIntMap<CompanionOperation> map = new UniqueIntMap<>(); static class UniqueIntMap<T> extends IntMap<T> { @Override @@ -289,8 +309,16 @@ public class Operations { map.put(MODIFIER_ROUNDED_CLIP_RECT, RoundedClipRectModifierOperation::read); map.put(MODIFIER_CLIP_RECT, ClipRectModifierOperation::read); map.put(MODIFIER_CLICK, ClickModifierOperation::read); - map.put(MODIFIER_CLICK_END, ClickModifierEnd::read); + map.put(MODIFIER_TOUCH_DOWN, TouchDownModifierOperation::read); + map.put(MODIFIER_TOUCH_UP, TouchUpModifierOperation::read); + map.put(MODIFIER_TOUCH_CANCEL, TouchCancelModifierOperation::read); map.put(MODIFIER_VISIBILITY, ComponentVisibilityOperation::read); + map.put(MODIFIER_OFFSET, OffsetModifierOperation::read); + map.put(MODIFIER_ZINDEX, ZIndexModifierOperation::read); + map.put(MODIFIER_GRAPHICS_LAYER, GraphicsLayerModifierOperation::read); + + map.put(OPERATIONS_LIST_END, OperationsListEnd::read); + map.put(HOST_ACTION, HostActionOperation::read); map.put(HOST_NAMED_ACTION, HostNamedActionOperation::read); map.put(VALUE_INTEGER_CHANGE_ACTION, ValueIntegerChangeActionOperation::read); @@ -298,6 +326,7 @@ public class Operations { VALUE_INTEGER_EXPRESSION_CHANGE_ACTION, ValueIntegerExpressionChangeActionOperation::read); map.put(VALUE_STRING_CHANGE_ACTION, ValueStringChangeActionOperation::read); + map.put(VALUE_FLOAT_CHANGE_ACTION, ValueFloatChangeActionOperation::read); map.put(LAYOUT_ROOT, RootLayoutComponent::read); map.put(LAYOUT_CONTENT, LayoutComponentContent::read); @@ -315,5 +344,6 @@ public class Operations { map.put(DATA_MAP_LOOKUP, DataMapLookup::read); map.put(TEXT_MEASURE, TextMeasure::read); map.put(TEXT_LENGTH, TextLength::read); + map.put(TOUCH_EXPRESSION, TouchExpression::read); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java index 13d6f783a586..1a71afe068bd 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java @@ -271,4 +271,24 @@ public abstract class PaintContext { public void needsRepaint() { mNeedsRepaint = true; } + + public abstract void startGraphicsLayer(int w, int h); + + public abstract void setGraphicsLayer( + float scaleX, + float scaleY, + float rotationX, + float rotationY, + float rotationZ, + float shadowElevation, + float transformOriginX, + float transformOriginY, + float alpha, + int renderEffectId); + + public abstract void endGraphicsLayer(); + + public boolean isVisualDebug() { + return mContext.isVisualDebug(); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java index 9b7b50f775b0..049e47744ce8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core; +import android.annotation.NonNull; + /** * PaintOperation interface, used for operations aimed at painting (while any operation _can_ paint, * this make it a little more explicit) @@ -22,7 +24,7 @@ package com.android.internal.widget.remotecompose.core; public abstract class PaintOperation implements Operation { @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { if (context.getMode() == RemoteContext.ContextMode.PAINT) { PaintContext paintContext = context.getPaintContext(); if (paintContext != null) { @@ -31,6 +33,7 @@ public abstract class PaintOperation implements Operation { } } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/Platform.java b/core/java/com/android/internal/widget/remotecompose/core/Platform.java index 6725e7e6ac2b..7fbcfae54bcb 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Platform.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java @@ -15,16 +15,30 @@ */ package com.android.internal.widget.remotecompose.core; +import android.annotation.Nullable; + /** Services that are needed to be provided by the platform during encoding. */ public interface Platform { + @Nullable byte[] imageToByteArray(Object image); int getImageWidth(Object image); int getImageHeight(Object image); + @Nullable float[] pathToFloatArray(Object path); + enum LogCategory { + DEBUG, + INFO, + WARN, + ERROR, + TODO, + } + + void log(LogCategory category, String message); + Platform None = new Platform() { @Override @@ -46,5 +60,8 @@ public interface Platform { public float[] pathToFloatArray(Object path) { throw new UnsupportedOperationException(); } + + @Override + public void log(LogCategory category, String message) {} }; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java index 5b5adc28a676..7d9439dd186b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.operations.BitmapData; import com.android.internal.widget.remotecompose.core.operations.ClickArea; import com.android.internal.widget.remotecompose.core.operations.ClipPath; @@ -64,6 +67,7 @@ import com.android.internal.widget.remotecompose.core.operations.TextLookupInt; import com.android.internal.widget.remotecompose.core.operations.TextMeasure; import com.android.internal.widget.remotecompose.core.operations.TextMerge; import com.android.internal.widget.remotecompose.core.operations.Theme; +import com.android.internal.widget.remotecompose.core.operations.TouchExpression; import com.android.internal.widget.remotecompose.core.operations.Utils; import com.android.internal.widget.remotecompose.core.operations.layout.CanvasContent; import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd; @@ -81,8 +85,11 @@ import com.android.internal.widget.remotecompose.core.operations.layout.managers import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BackgroundModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BorderModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.OffsetModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation; import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap; import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation; @@ -111,7 +118,7 @@ public class RemoteComposeBuffer { public static final int EASING_EASE_OUT_BOUNCE = FloatAnimation.EASE_OUT_BOUNCE; public static final int EASING_EASE_OUT_ELASTIC = FloatAnimation.EASE_OUT_ELASTIC; WireBuffer mBuffer = new WireBuffer(); - Platform mPlatform = null; + @Nullable Platform mPlatform = null; RemoteComposeState mRemoteComposeState; private static final boolean DEBUG = false; @@ -143,6 +150,7 @@ public class RemoteComposeBuffer { return mLastComponentId; } + @Nullable public Platform getPlatform() { return mPlatform; } @@ -172,7 +180,11 @@ public class RemoteComposeBuffer { * @param capabilities bitmask indicating needed capabilities (unused for now) */ public void header( - int width, int height, String contentDescription, float density, long capabilities) { + int width, + int height, + @Nullable String contentDescription, + float density, + long capabilities) { Header.apply(mBuffer, width, height, density, capabilities); int contentDescriptionId = 0; if (contentDescription != null) { @@ -219,7 +231,7 @@ public class RemoteComposeBuffer { int dstTop, int dstRight, int dstBottom, - String contentDescription) { + @Nullable String contentDescription) { int imageId = mRemoteComposeState.dataGetId(image); if (imageId == -1) { imageId = mRemoteComposeState.cacheData(image); @@ -267,7 +279,7 @@ public class RemoteComposeBuffer { * * @param text the string to inject in the buffer */ - public int addText(String text) { + public int addText(@NonNull String text) { int id = mRemoteComposeState.dataGetId(text); if (id == -1) { id = mRemoteComposeState.cacheData(text); @@ -289,12 +301,12 @@ public class RemoteComposeBuffer { */ public void addClickArea( int id, - String contentDescription, + @Nullable String contentDescription, float left, float top, float right, float bottom, - String metadata) { + @Nullable String metadata) { int contentDescriptionId = 0; if (contentDescription != null) { contentDescriptionId = addText(contentDescription); @@ -380,7 +392,7 @@ public class RemoteComposeBuffer { float top, float right, float bottom, - String contentDescription) { + @Nullable String contentDescription) { int imageId = mRemoteComposeState.dataGetId(image); if (imageId == -1) { imageId = mRemoteComposeState.cacheData(image); @@ -411,7 +423,7 @@ public class RemoteComposeBuffer { float top, float right, float bottom, - String contentDescription) { + @Nullable String contentDescription) { int contentDescriptionId = 0; if (contentDescription != null) { contentDescriptionId = addText(contentDescription); @@ -445,7 +457,7 @@ public class RemoteComposeBuffer { float dstBottom, int scaleType, float scaleFactor, - String contentDescription) { + @Nullable String contentDescription) { int imageId = mRemoteComposeState.dataGetId(image); if (imageId == -1) { imageId = mRemoteComposeState.cacheData(image); @@ -500,7 +512,7 @@ public class RemoteComposeBuffer { * @param image drawScaledBitmap * @return id of the image useful with */ - public int addBitmap(Object image, String name) { + public int addBitmap(Object image, @NonNull String name) { int imageId = mRemoteComposeState.dataGetId(image); if (imageId == -1) { imageId = mRemoteComposeState.cacheData(image); @@ -521,7 +533,7 @@ public class RemoteComposeBuffer { * @param id of the Bitmap * @param name Name of the color */ - public void setBitmapName(int id, String name) { + public void setBitmapName(int id, @NonNull String name) { NamedVariable.apply(mBuffer, id, NamedVariable.IMAGE_TYPE, name); } @@ -551,7 +563,7 @@ public class RemoteComposeBuffer { float dstBottom, int scaleType, float scaleFactor, - String contentDescription) { + @Nullable String contentDescription) { int contentDescriptionId = 0; if (contentDescription != null) { contentDescriptionId = addText(contentDescription); @@ -669,7 +681,7 @@ public class RemoteComposeBuffer { * @param hOffset The distance along the path to add to the text's starting position * @param vOffset The distance above(-) or below(+) the path to position the text */ - public void addDrawTextOnPath(String text, Object path, float hOffset, float vOffset) { + public void addDrawTextOnPath(@NonNull String text, Object path, float hOffset, float vOffset) { int pathId = mRemoteComposeState.dataGetId(path); if (pathId == -1) { // never been seen before pathId = addPathData(path); @@ -692,7 +704,7 @@ public class RemoteComposeBuffer { * @param rtl Draw RTTL */ public void addDrawTextRun( - String text, + @NonNull String text, int start, int end, int contextStart, @@ -749,7 +761,8 @@ public class RemoteComposeBuffer { * @param panY position text -1.0=above, 0.0=center, 1.0=below, Nan=baseline * @param flags 1 = RTL */ - public void drawTextAnchored(String text, float x, float y, float panX, float panY, int flags) { + public void drawTextAnchored( + @NonNull String text, float x, float y, float panX, float panY, int flags) { int textId = addText(text); DrawTextAnchored.apply(mBuffer, textId, x, y, panX, panY, flags); } @@ -760,7 +773,7 @@ public class RemoteComposeBuffer { * @param text * @return */ - public int createTextId(String text) { + public int createTextId(@NonNull String text) { return addText(text); } @@ -891,7 +904,7 @@ public class RemoteComposeBuffer { * * @param paint */ - public void addPaint(PaintBundle paint) { + public void addPaint(@NonNull PaintBundle paint) { PaintData.apply(mBuffer, paint); } @@ -912,7 +925,8 @@ public class RemoteComposeBuffer { } } - public static void readNextOperation(WireBuffer buffer, ArrayList<Operation> operations) { + public static void readNextOperation( + @NonNull WireBuffer buffer, ArrayList<Operation> operations) { int opId = buffer.readByte(); if (DEBUG) { Utils.log(">> " + opId); @@ -924,6 +938,7 @@ public class RemoteComposeBuffer { operation.read(buffer, operations); } + @NonNull RemoteComposeBuffer copy() { ArrayList<Operation> operations = new ArrayList<>(); inflateFromBuffer(operations); @@ -935,33 +950,38 @@ public class RemoteComposeBuffer { Theme.apply(mBuffer, theme); } + @NonNull static String version() { return "v1.0"; } - public static RemoteComposeBuffer fromFile(String path, RemoteComposeState remoteComposeState) - throws IOException { + @NonNull + public static RemoteComposeBuffer fromFile( + @NonNull String path, RemoteComposeState remoteComposeState) throws IOException { RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState); read(new File(path), buffer); return buffer; } - public RemoteComposeBuffer fromFile(File file, RemoteComposeState remoteComposeState) + @NonNull + public RemoteComposeBuffer fromFile(@NonNull File file, RemoteComposeState remoteComposeState) throws IOException { RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState); read(file, buffer); return buffer; } + @NonNull public static RemoteComposeBuffer fromInputStream( - InputStream inputStream, RemoteComposeState remoteComposeState) { + @NonNull InputStream inputStream, RemoteComposeState remoteComposeState) { RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState); read(inputStream, buffer); return buffer; } + @NonNull RemoteComposeBuffer copyFromOperations( - ArrayList<Operation> operations, RemoteComposeBuffer buffer) { + @NonNull ArrayList<Operation> operations, @NonNull RemoteComposeBuffer buffer) { for (Operation operation : operations) { operation.write(buffer.mBuffer); @@ -975,7 +995,7 @@ public class RemoteComposeBuffer { * @param buffer a RemoteComposeBuffer * @param file a target file */ - public void write(RemoteComposeBuffer buffer, File file) { + public void write(@NonNull RemoteComposeBuffer buffer, @NonNull File file) { try { FileOutputStream fd = new FileOutputStream(file); fd.write(buffer.mBuffer.getBuffer(), 0, buffer.mBuffer.getSize()); @@ -986,12 +1006,12 @@ public class RemoteComposeBuffer { } } - static void read(File file, RemoteComposeBuffer buffer) throws IOException { + static void read(@NonNull File file, @NonNull RemoteComposeBuffer buffer) throws IOException { FileInputStream fd = new FileInputStream(file); read(fd, buffer); } - public static void read(InputStream fd, RemoteComposeBuffer buffer) { + public static void read(@NonNull InputStream fd, @NonNull RemoteComposeBuffer buffer) { try { byte[] bytes = readAllBytes(fd); buffer.reset(bytes.length); @@ -1002,7 +1022,7 @@ public class RemoteComposeBuffer { } } - private static byte[] readAllBytes(InputStream is) throws IOException { + private static byte[] readAllBytes(@NonNull InputStream is) throws IOException { byte[] buff = new byte[32 * 1024]; // moderate size buff to start int red = 0; while (true) { @@ -1176,20 +1196,59 @@ public class RemoteComposeBuffer { * @param value A RPN style float operation i.e. "4, 3, ADD" outputs 7 * @return NaN id of the result of the calculation */ - public float addAnimatedFloat(float... value) { + public float addAnimatedFloat(@NonNull float... value) { int id = mRemoteComposeState.cacheData(value); FloatExpression.apply(mBuffer, id, value, null); return Utils.asNan(id); } /** + * Add a touch handle system + * + * @param value the default value + * @param min the minimum value + * @param max the maximum value + * @param velocityId the id for the velocity TODO support in v2 + * @param exp The Float Expression + * @param touchMode the touch up handling behaviour + * @param touchSpec the touch up handling parameters + * @param easingSpec the easing parameter TODO support in v2 + * @return id of the variable to be used controlled by touch handling + */ + public float addTouchExpression( + float value, + float min, + float max, + float velocityId, + int touchEffects, + float[] exp, + int touchMode, + float[] touchSpec, + float[] easingSpec) { + int id = mRemoteComposeState.nextId(); + TouchExpression.apply( + mBuffer, + id, + value, + min, + max, + velocityId, + touchEffects, + exp, + touchMode, + touchSpec, + easingSpec); + return Utils.asNan(id); + } + + /** * Add a float that is a computation based on variables. see packAnimation * * @param value A RPN style float operation i.e. "4, 3, ADD" outputs 7 * @param animation Array of floats that represents animation * @return NaN id of the result of the calculation */ - public float addAnimatedFloat(float[] value, float[] animation) { + public float addAnimatedFloat(@NonNull float[] value, float[] animation) { int id = mRemoteComposeState.cacheData(value); FloatExpression.apply(mBuffer, id, value, animation); return Utils.asNan(id); @@ -1228,7 +1287,7 @@ public class RemoteComposeBuffer { * @param values * @return the id of the array, encoded as a float NaN */ - public float addFloatArray(float[] values) { + public float addFloatArray(@NonNull float[] values) { int id = mRemoteComposeState.cacheData(values, NanMap.TYPE_ARRAY); DataListFloat.apply(mBuffer, id, values); return Utils.asNan(id); @@ -1240,7 +1299,7 @@ public class RemoteComposeBuffer { * @param values array of floats to be individually stored * @return id of the list */ - public float addFloatList(float[] values) { + public float addFloatList(@NonNull float[] values) { int[] listId = new int[values.length]; for (int i = 0; i < listId.length; i++) { listId[i] = mRemoteComposeState.cacheFloat(values[i]); @@ -1255,7 +1314,7 @@ public class RemoteComposeBuffer { * @param listId array id to be stored * @return id of the list */ - public float addList(int[] listId) { + public float addList(@NonNull int[] listId) { int id = mRemoteComposeState.cacheData(listId, NanMap.TYPE_ARRAY); DataListIds.apply(mBuffer, id, listId); return Utils.asNan(id); @@ -1268,7 +1327,7 @@ public class RemoteComposeBuffer { * @param values * @return the id of the map, encoded as a float NaN */ - public float addFloatMap(String[] keys, float[] values) { + public float addFloatMap(@NonNull String[] keys, @NonNull float[] values) { int[] listId = new int[values.length]; byte[] type = new byte[values.length]; for (int i = 0; i < listId.length; i++) { @@ -1286,7 +1345,7 @@ public class RemoteComposeBuffer { * @param listId * @return the id of the map, encoded as a float NaN */ - public int addMap(String[] keys, byte[] types, int[] listId) { + public int addMap(@NonNull String[] keys, byte[] types, int[] listId) { int id = mRemoteComposeState.cacheData(listId, NanMap.TYPE_ARRAY); DataMapIds.apply(mBuffer, id, keys, types, listId); return id; @@ -1331,7 +1390,7 @@ public class RemoteComposeBuffer { * @param value array of values to calculate maximum 32 * @return the id as an integer */ - public int addIntegerExpression(int mask, int[] value) { + public int addIntegerExpression(int mask, @NonNull int[] value) { int id = mRemoteComposeState.cacheData(value); IntegerExpression.apply(mBuffer, id, mask, value); return id; @@ -1474,7 +1533,7 @@ public class RemoteComposeBuffer { * @param id of the color * @param name Name of the color */ - public void setColorName(int id, String name) { + public void setColorName(int id, @NonNull String name) { NamedVariable.apply(mBuffer, id, NamedVariable.COLOR_TYPE, name); } @@ -1484,7 +1543,7 @@ public class RemoteComposeBuffer { * @param id of the string * @param name name of the string */ - public void setStringName(int id, String name) { + public void setStringName(int id, @NonNull String name) { NamedVariable.apply(mBuffer, id, NamedVariable.STRING_TYPE, name); } @@ -1576,6 +1635,72 @@ public class RemoteComposeBuffer { } /** + * Add an offset modifier + * + * @param x x offset + * @param y y offset + */ + public void addModifierOffset(float x, float y) { + OffsetModifierOperation.apply(mBuffer, x, y); + } + + /** + * Add a zIndex modifier + * + * @param value z-Index value + */ + public void addModifierZIndex(float value) { + ZIndexModifierOperation.apply(mBuffer, value); + } + + /** + * Add a graphics layer + * + * @param scaleX + * @param scaleY + * @param rotationX + * @param rotationY + * @param rotationZ + * @param shadowElevation + * @param transformOriginX + * @param transformOriginY + */ + public void addModifierGraphicsLayer( + float scaleX, + float scaleY, + float rotationX, + float rotationY, + float rotationZ, + float shadowElevation, + float transformOriginX, + float transformOriginY, + float alpha, + float cameraDistance, + int blendMode, + int spotShadowColorId, + int ambientShadowColorId, + int colorFilterId, + int renderEffectId) { + GraphicsLayerModifierOperation.apply( + mBuffer, + scaleX, + scaleY, + rotationX, + rotationY, + rotationZ, + shadowElevation, + transformOriginX, + transformOriginY, + alpha, + cameraDistance, + blendMode, + spotShadowColorId, + ambientShadowColorId, + colorFilterId, + renderEffectId); + } + + /** * Sets the clip based on rounded clip rect * * @param topStart @@ -1721,7 +1846,8 @@ public class RemoteComposeBuffer { float fontSize, int fontStyle, float fontWeight, - String fontFamily) { + @Nullable String fontFamily, + int textAlign) { mLastComponentId = getComponentId(componentId); int fontFamilyId = -1; if (fontFamily != null) { @@ -1736,6 +1862,11 @@ public class RemoteComposeBuffer { fontSize, fontStyle, fontWeight, - fontFamilyId); + fontFamilyId, + textAlign); + } + + public int createID(int type) { + return mRemoteComposeState.nextId(type); } } 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 51445f2ff31d..303932814909 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.operations.utilities.ArrayAccess; import com.android.internal.widget.remotecompose.core.operations.utilities.CollectionsAccess; import com.android.internal.widget.remotecompose.core.operations.utilities.DataMap; @@ -50,10 +53,11 @@ public class RemoteComposeState implements CollectionsAccess { private final boolean[] mDataOverride = new boolean[MAX_DATA]; private final boolean[] mIntegerOverride = new boolean[MAX_DATA]; + private final boolean[] mFloatOverride = new boolean[MAX_DATA]; private int mNextId = START_ID; - private int[] mIdMaps = new int[] {START_ID, NanMap.START_VAR, NanMap.START_ARRAY}; - private RemoteContext mRemoteContext = null; + @NonNull private int[] mIdMaps = new int[] {START_ID, NanMap.START_VAR, NanMap.START_ARRAY}; + @Nullable private RemoteContext mRemoteContext = null; /** * Get Object based on id. The system will cache things like bitmaps Paths etc. They can be @@ -62,6 +66,7 @@ public class RemoteComposeState implements CollectionsAccess { * @param id * @return */ + @Nullable public Object getFromId(int id) { return mIntDataMap.get(id); } @@ -158,10 +163,28 @@ public class RemoteComposeState implements CollectionsAccess { /** Insert an float item in the cache */ public void updateFloat(int id, float value) { + if (!mFloatOverride[id]) { + float previous = mFloatMap.get(id); + if (previous != value) { + mFloatMap.put(id, value); + mIntegerMap.put(id, (int) value); + updateListeners(id); + } + } + } + + /** + * Adds a float Override. + * + * @param id + * @param value the new value + */ + public void overrideFloat(int id, float value) { float previous = mFloatMap.get(id); if (previous != value) { mFloatMap.put(id, value); mIntegerMap.put(id, (int) value); + mFloatOverride[id] = true; updateListeners(id); } } @@ -294,6 +317,16 @@ public class RemoteComposeState implements CollectionsAccess { } /** + * Clear the float override + * + * @param id the float id to clear + */ + public void clearFloatOverride(int id) { + mFloatOverride[id] = false; + updateListeners(id); + } + + /** * Method to determine if a cached value has been written to the documents WireBuffer based on * its id. */ @@ -322,7 +355,8 @@ public class RemoteComposeState implements CollectionsAccess { } /** - * Get the next available id + * Get the next available id 0 is normal (float,int,String,color) 1 is VARIABLES 2 is + * collections * * @return */ @@ -342,8 +376,8 @@ public class RemoteComposeState implements CollectionsAccess { mNextId = id; } - IntMap<ArrayList<VariableSupport>> mVarListeners = new IntMap<>(); - ArrayList<VariableSupport> mAllVarListeners = new ArrayList<>(); + @NonNull IntMap<ArrayList<VariableSupport>> mVarListeners = new IntMap<>(); + @NonNull ArrayList<VariableSupport> mAllVarListeners = new ArrayList<>(); private void add(int id, VariableSupport variableSupport) { ArrayList<VariableSupport> v = mVarListeners.get(id); diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java index 1066e7d9f617..23cc5b89d916 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.operations.FloatExpression; import com.android.internal.widget.remotecompose.core.operations.ShaderData; import com.android.internal.widget.remotecompose.core.operations.Theme; @@ -40,10 +42,12 @@ public abstract class RemoteContext { protected CoreDocument mDocument; public RemoteComposeState mRemoteComposeState; long mStart = System.nanoTime(); // todo This should be set at a hi level - protected PaintContext mPaintContext = null; + @Nullable protected PaintContext mPaintContext = null; + protected float mDensity = 2.75f; + ContextMode mMode = ContextMode.UNSET; - boolean mDebug = false; + int mDebug = 0; private int mTheme = Theme.UNSPECIFIED; @@ -56,6 +60,14 @@ public abstract class RemoteContext { public Component lastComponent; public long currentTime = 0L; + public float getDensity() { + return mDensity; + } + + public void setDensity(float density) { + mDensity = density; + } + public boolean isAnimationEnabled() { return mAnimate; } @@ -173,12 +185,22 @@ public abstract class RemoteContext { public abstract void runAction(int id, String metadata); - public abstract void runNamedAction(int textId); + // TODO: we might add an interface to group all valid parameter types + public abstract void runNamedAction(int textId, Object value); public abstract void putObject(int mId, Object command); public abstract Object getObject(int mId); + public void addTouchListener(TouchListener touchExpression) {} + + /** + * Vibrate the device + * + * @param type 0 = none, 1-21 ,see HapticFeedbackConstants + */ + public abstract void hapticEffect(int type); + /** * The context can be used in a few different mode, allowing operations to skip being executed: * - UNSET : all operations will get executed - DATA : only operations dealing with DATA (eg @@ -206,6 +228,7 @@ public abstract class RemoteContext { this.mMode = mode; } + @Nullable public PaintContext getPaintContext() { return mPaintContext; } @@ -219,10 +242,14 @@ public abstract class RemoteContext { } public boolean isDebug() { - return mDebug; + return mDebug == 1; } - public void setDebug(boolean debug) { + public boolean isVisualDebug() { + return mDebug == 2; + } + + public void setDebug(int debug) { this.mDebug = debug; } @@ -314,6 +341,14 @@ public abstract class RemoteContext { public abstract void loadFloat(int id, float value); /** + * Override an existing float value + * + * @param id + * @param value + */ + public abstract void overrideFloat(int id, float value); + + /** * Load a integer * * @param id id of the integer @@ -321,8 +356,20 @@ public abstract class RemoteContext { */ public abstract void loadInteger(int id, int value); + /** + * Override an existing int value + * + * @param id + * @param value + */ public abstract void overrideInteger(int id, int value); + /** + * Override an existing text value + * + * @param id + * @param valueId + */ public abstract void overrideText(int id, int valueId); /** @@ -400,6 +447,25 @@ public abstract class RemoteContext { public static final int ID_OFFSET_TO_UTC = 10; public static final int ID_WEEK_DAY = 11; public static final int ID_DAY_OF_MONTH = 12; + public static final int ID_TOUCH_POS_X = 13; + public static final int ID_TOUCH_POS_Y = 14; + + public static final int ID_TOUCH_VEL_X = 15; + public static final int ID_TOUCH_VEL_Y = 16; + + public static final int ID_ACCELERATION_X = 17; + public static final int ID_ACCELERATION_Y = 18; + public static final int ID_ACCELERATION_Z = 19; + + public static final int ID_GYRO_ROT_X = 20; + public static final int ID_GYRO_ROT_Y = 21; + public static final int ID_GYRO_ROT_Z = 22; + + public static final int ID_MAGNETIC_X = 23; + public static final int ID_MAGNETIC_Y = 24; + public static final int ID_MAGNETIC_Z = 25; + + public static final int ID_LIGHT = 26; /** CONTINUOUS_SEC is seconds from midnight looping every hour 0-3600 */ public static final float FLOAT_CONTINUOUS_SEC = Utils.asNan(ID_CONTINUOUS_SEC); @@ -426,9 +492,52 @@ public abstract class RemoteContext { public static final float FLOAT_WINDOW_HEIGHT = Utils.asNan(ID_WINDOW_HEIGHT); public static final float FLOAT_COMPONENT_WIDTH = Utils.asNan(ID_COMPONENT_WIDTH); public static final float FLOAT_COMPONENT_HEIGHT = Utils.asNan(ID_COMPONENT_HEIGHT); - // ID_OFFSET_TO_UTC is the offset from UTC in sec (typically / 3600f) + + /** ID_OFFSET_TO_UTC is the offset from UTC in sec (typically / 3600f) */ public static final float FLOAT_OFFSET_TO_UTC = Utils.asNan(ID_OFFSET_TO_UTC); + /** TOUCH_POS_X is the x position of the touch */ + public static final float FLOAT_TOUCH_POS_X = Utils.asNan(ID_TOUCH_POS_X); + + /** TOUCH_POS_Y is the y position of the touch */ + public static final float FLOAT_TOUCH_POS_Y = Utils.asNan(ID_TOUCH_POS_Y); + + /** TOUCH_VEL_X is the x velocity of the touch */ + public static final float FLOAT_TOUCH_VEL_X = Utils.asNan(ID_TOUCH_VEL_X); + + /** TOUCH_VEL_Y is the x velocity of the touch */ + public static final float FLOAT_TOUCH_VEL_Y = Utils.asNan(ID_TOUCH_VEL_Y); + + /** X acceleration sensor value in M/s^2 */ + public static final float FLOAT_ACCELERATION_X = Utils.asNan(ID_ACCELERATION_X); + + /** Y acceleration sensor value in M/s^2 */ + public static final float FLOAT_ACCELERATION_Y = Utils.asNan(ID_ACCELERATION_Y); + + /** Z acceleration sensor value in M/s^2 */ + public static final float FLOAT_ACCELERATION_Z = Utils.asNan(ID_ACCELERATION_Z); + + /** X Gyroscope rotation rate sensor value in radians/second */ + public static final float FLOAT_GYRO_ROT_X = Utils.asNan(ID_GYRO_ROT_X); + + /** Y Gyroscope rotation rate sensor value in radians/second */ + public static final float FLOAT_GYRO_ROT_Y = Utils.asNan(ID_GYRO_ROT_Y); + + /** Z Gyroscope rotation rate sensor value in radians/second */ + public static final float FLOAT_GYRO_ROT_Z = Utils.asNan(ID_GYRO_ROT_Z); + + /** Ambient magnetic field in X. sensor value in micro-Tesla (uT) */ + public static final float FLOAT_MAGNETIC_X = Utils.asNan(ID_MAGNETIC_X); + + /** Ambient magnetic field in Y. sensor value in micro-Tesla (uT) */ + public static final float FLOAT_MAGNETIC_Y = Utils.asNan(ID_MAGNETIC_Y); + + /** Ambient magnetic field in Z. sensor value in micro-Tesla (uT) */ + public static final float FLOAT_MAGNETIC_Z = Utils.asNan(ID_MAGNETIC_Z); + + /** Ambient light level in SI lux */ + public static final float FLOAT_LIGHT = Utils.asNan(ID_LIGHT); + /////////////////////////////////////////////////////////////////////////////////////////////// // Click handling /////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java index fa0cf3f455c4..14aed2f0c173 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java +++ b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core; +import android.annotation.NonNull; + import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneId; @@ -27,7 +29,7 @@ public class TimeVariables { * * @param context */ - public void updateTime(RemoteContext context) { + public void updateTime(@NonNull RemoteContext context) { LocalDateTime dateTime = LocalDateTime.now(ZoneId.systemDefault()); // TODO, pass in a timezone explicitly? // This define the time in the format diff --git a/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java new file mode 100644 index 000000000000..3dda678c2a3a --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core; + +public interface TouchListener { + void touchDown(RemoteContext context, float x, float y); + + void touchUp(RemoteContext context, float x, float y, float dx, float dy); + + void touchDrag(RemoteContext context, float x, float y); +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java index c71b4901ca78..738e42baae0b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core; +import android.annotation.NonNull; + import java.util.Arrays; /** The base communication buffer capable of encoding and decoding various types */ @@ -184,11 +186,13 @@ public class WireBuffer { return b; } + @NonNull public String readUTF8() { byte[] stringBuffer = readBuffer(); return new String(stringBuffer); } + @NonNull public String readUTF8(int maxSize) { byte[] stringBuffer = readBuffer(maxSize); return new String(stringBuffer); @@ -250,7 +254,7 @@ public class WireBuffer { writeLong(Double.doubleToRawLongBits(value)); } - public void writeBuffer(byte[] b) { + public void writeBuffer(@NonNull byte[] b) { resize(b.length + 4); writeInt(b.length); for (int i = 0; i < b.length; i++) { @@ -259,7 +263,7 @@ public class WireBuffer { mSize += b.length; } - public void writeUTF8(String content) { + public void writeUTF8(@NonNull String content) { byte[] buffer = content.getBytes(); writeBuffer(buffer); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java index c33ae244b923..5edecaa01f82 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.documentation; +import android.annotation.NonNull; + import java.util.ArrayList; public class DocumentedOperation { @@ -40,12 +42,13 @@ public class DocumentedOperation { boolean mWIP; String mTextExamples; - ArrayList<StringPair> mExamples = new ArrayList<>(); - ArrayList<OperationField> mFields = new ArrayList<>(); - String mVarSize = ""; + @NonNull ArrayList<StringPair> mExamples = new ArrayList<>(); + @NonNull ArrayList<OperationField> mFields = new ArrayList<>(); + @NonNull String mVarSize = ""; int mExamplesWidth = 100; int mExamplesHeight = 100; + @NonNull public static String getType(int type) { switch (type) { case INT: @@ -85,6 +88,7 @@ public class DocumentedOperation { this(category, id, name, false); } + @NonNull public ArrayList<OperationField> getFields() { return mFields; } @@ -105,6 +109,7 @@ public class DocumentedOperation { return mWIP; } + @NonNull public String getVarSize() { return mVarSize; } @@ -129,6 +134,7 @@ public class DocumentedOperation { return mTextExamples; } + @NonNull public ArrayList<StringPair> getExamples() { return mExamples; } @@ -141,16 +147,19 @@ public class DocumentedOperation { return mExamplesHeight; } + @NonNull public DocumentedOperation field(int type, String name, String description) { mFields.add(new OperationField(type, name, description)); return this; } + @NonNull public DocumentedOperation field(int type, String name, String varSize, String description) { mFields.add(new OperationField(type, name, varSize, description)); return this; } + @NonNull public DocumentedOperation possibleValues(String name, int value) { if (!mFields.isEmpty()) { mFields.get(mFields.size() - 1).possibleValue(name, "" + value); @@ -158,21 +167,25 @@ public class DocumentedOperation { return this; } + @NonNull public DocumentedOperation description(String description) { mDescription = description; return this; } + @NonNull public DocumentedOperation examples(String examples) { mTextExamples = examples; return this; } + @NonNull public DocumentedOperation exampleImage(String name, String imagePath) { mExamples.add(new StringPair(name, imagePath)); return this; } + @NonNull public DocumentedOperation examplesDimension(int width, int height) { mExamplesWidth = width; mExamplesHeight = height; diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java index c77048391405..cbb5ca9821c3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java +++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java @@ -15,15 +15,18 @@ */ package com.android.internal.widget.remotecompose.core.documentation; +import android.annotation.NonNull; +import android.annotation.Nullable; + import java.util.ArrayList; public class OperationField { int mType; String mName; String mDescription; - String mVarSize = null; + @Nullable String mVarSize = null; - ArrayList<StringPair> mPossibleValues = new ArrayList<>(); + @NonNull ArrayList<StringPair> mPossibleValues = new ArrayList<>(); public OperationField(int type, String name, String description) { mType = type; @@ -50,6 +53,7 @@ public class OperationField { return mDescription; } + @NonNull public ArrayList<StringPair> getPossibleValues() { return mPossibleValues; } @@ -62,6 +66,7 @@ public class OperationField { return !mPossibleValues.isEmpty(); } + @Nullable public String getVarSize() { return mVarSize; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java index 20ba8c313b47..8da0e184cbe7 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -58,15 +60,17 @@ public class BitmapData implements Operation, SerializableToString { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mImageId, mImageWidth, mImageHeight, mBitmap); } + @NonNull @Override public String toString() { return "BITMAP DATA " + mImageId; } + @NonNull public static String name() { return CLASS_NAME; } @@ -75,7 +79,12 @@ public class BitmapData implements Operation, SerializableToString { return OP_CODE; } - public static void apply(WireBuffer buffer, int imageId, int width, int height, byte[] bitmap) { + public static void apply( + @NonNull WireBuffer buffer, + int imageId, + int width, + int height, + @NonNull byte[] bitmap) { buffer.start(OP_CODE); buffer.writeInt(imageId); buffer.writeInt(width); @@ -83,7 +92,7 @@ public class BitmapData implements Operation, SerializableToString { buffer.writeBuffer(bitmap); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int imageId = buffer.readInt(); int width = buffer.readInt(); int height = buffer.readInt(); @@ -97,7 +106,7 @@ public class BitmapData implements Operation, SerializableToString { operations.add(new BitmapData(imageId, width, height, bitmap)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Data Operations", OP_CODE, CLASS_NAME) .description("Bitmap data") .field(DocumentedOperation.INT, "id", "id of bitmap data") @@ -107,17 +116,18 @@ public class BitmapData implements Operation, SerializableToString { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.loadBitmap(mImageId, mImageWidth, mImageHeight, mBitmap); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append( indent, CLASS_NAME + " id " + mImageId + " (" + mImageWidth + "x" + mImageHeight + ")"); 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 8b9e5a8d7625..83d0ac7a1eb2 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 @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteComposeOperation; @@ -67,10 +69,11 @@ public class ClickArea implements RemoteComposeOperation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mId, mContentDescription, mLeft, mTop, mRight, mBottom, mMetadata); } + @NonNull @Override public String toString() { return "CLICK_AREA <" @@ -97,18 +100,20 @@ public class ClickArea implements RemoteComposeOperation { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { if (context.getMode() != RemoteContext.ContextMode.DATA) { return; } context.addClickArea(mId, mContentDescription, mLeft, mTop, mRight, mBottom, mMetadata); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); } + @NonNull public static String name() { return CLASS_NAME; } @@ -118,7 +123,7 @@ public class ClickArea implements RemoteComposeOperation { } public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, int id, int contentDescription, float left, @@ -136,7 +141,7 @@ public class ClickArea implements RemoteComposeOperation { buffer.writeInt(metadata); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int id = buffer.readInt(); int contentDescription = buffer.readInt(); float left = buffer.readFloat(); @@ -149,7 +154,7 @@ public class ClickArea implements RemoteComposeOperation { operations.add(clickArea); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description("Define a region you can click on") .field(DocumentedOperation.FLOAT, "left", "The left side of the region") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java index 96b600acc971..db93829586bb 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -57,16 +59,17 @@ public class ClipPath extends PaintOperation { public static final int UNDEFINED = PATH_CLIP_UNDEFINED; @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mId); } + @NonNull @Override public String toString() { return "ClipPath " + mId + ";"; } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int pack = buffer.readInt(); int id = pack & 0xFFFFF; int regionOp = pack >> 24; @@ -74,6 +77,7 @@ public class ClipPath extends PaintOperation { operations.add(op); } + @NonNull public static String name() { return CLASS_NAME; } @@ -82,19 +86,19 @@ public class ClipPath extends PaintOperation { return OP_CODE; } - public static void apply(WireBuffer buffer, int id) { + public static void apply(@NonNull WireBuffer buffer, int id) { buffer.start(OP_CODE); buffer.writeInt(id); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description("Intersect the current clip with the path") .field(DocumentedOperation.INT, "id", "id of the path"); } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.clipPath(mId, mRegionOp); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java index b101bfb2d151..df54fb1ed834 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -29,7 +31,7 @@ public class ClipRect extends DrawBase4 { public static final int OP_CODE = Operations.CLIP_RECT; public static final String CLASS_NAME = "ClipRect"; - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Maker m = ClipRect::new; read(m, buffer, operations); } @@ -38,16 +40,17 @@ public class ClipRect extends DrawBase4 { return OP_CODE; } + @NonNull public static String name() { return CLASS_NAME; } @Override - protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) { + protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) { apply(buffer, v1, v2, v3, v4); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Expressions Operations", OP_CODE, CLASS_NAME) .description("Intersect the current clip with rectangle") .field( @@ -74,7 +77,7 @@ public class ClipRect extends DrawBase4 { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.clipRect(mX1, mY1, mX2, mY2); } @@ -87,7 +90,7 @@ public class ClipRect extends DrawBase4 { * @param x2 end x of the DrawOval * @param y2 end y of the DrawOval */ - public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) { + public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) { write(buffer, OP_CODE, x1, y1, x2, y2); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java index 19d80daf0c8f..929c9a603079 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -39,15 +41,17 @@ public class ColorConstant implements Operation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mColorId, mColor); } + @NonNull @Override public String toString() { return "ColorConstant[" + mColorId + "] = " + Utils.colorInt(mColor) + ""; } + @NonNull public static String name() { return CLASS_NAME; } @@ -63,19 +67,19 @@ public class ColorConstant implements Operation { * @param colorId * @param color */ - public static void apply(WireBuffer buffer, int colorId, int color) { + public static void apply(@NonNull WireBuffer buffer, int colorId, int color) { buffer.start(OP_CODE); buffer.writeInt(colorId); buffer.writeInt(color); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int colorId = buffer.readInt(); int color = buffer.readInt(); operations.add(new ColorConstant(colorId, color)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Expressions Operations", OP_CODE, CLASS_NAME) .description("Define a Color") .field(DocumentedOperation.INT, "id", "Id of the color") @@ -83,10 +87,11 @@ public class ColorConstant implements Operation { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.loadColor(mColorId, mColor); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java index b6041eaeacdc..3d840c5b8203 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -94,7 +96,7 @@ public class ColorExpression implements Operation, VariableSupport { } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { if (mMode == 4) { if (Float.isNaN(mHue)) { mOutHue = context.getFloat(Utils.idFromNan(mHue)); @@ -118,7 +120,7 @@ public class ColorExpression implements Operation, VariableSupport { } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (mMode == 4) { if (Float.isNaN(mHue)) { context.listensTo(Utils.idFromNan(mHue), this); @@ -143,7 +145,7 @@ public class ColorExpression implements Operation, VariableSupport { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { if (mMode == 4) { context.loadColor( mId, (mAlpha << 24) | (0xFFFFFF & Utils.hsvToRgb(mOutHue, mOutSat, mOutValue))); @@ -164,11 +166,12 @@ public class ColorExpression implements Operation, VariableSupport { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { int mode = mMode | (mAlpha << 16); apply(buffer, mId, mode, mColor1, mColor2, mTween); } + @NonNull @Override public String toString() { if (mMode == 4) { @@ -196,6 +199,7 @@ public class ColorExpression implements Operation, VariableSupport { + ")"; } + @NonNull public static String name() { return CLASS_NAME; } @@ -215,7 +219,7 @@ public class ColorExpression implements Operation, VariableSupport { * @param tween */ public static void apply( - WireBuffer buffer, int id, int mode, int color1, int color2, float tween) { + @NonNull WireBuffer buffer, int id, int mode, int color1, int color2, float tween) { buffer.start(OP_CODE); buffer.writeInt(id); buffer.writeInt(mode); @@ -224,7 +228,7 @@ public class ColorExpression implements Operation, VariableSupport { buffer.writeFloat(tween); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int id = buffer.readInt(); int mode = buffer.readInt(); int color1 = buffer.readInt(); @@ -234,7 +238,7 @@ public class ColorExpression implements Operation, VariableSupport { operations.add(new ColorExpression(id, mode, color1, color2, tween)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Expressions Operations", OP_CODE, CLASS_NAME) .description("A Color defined by an expression") .field(DocumentedOperation.INT, "id", "Id of the color") @@ -249,6 +253,7 @@ public class ColorExpression implements Operation, VariableSupport { .field(FLOAT, "tween", "32 bit ARGB color"); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java index 992972076839..142c97b24d2e 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java @@ -17,6 +17,9 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -43,10 +46,12 @@ public class ComponentValue implements Operation, SerializableToString { return OP_CODE; } + @NonNull public static String name() { return CLASS_NAME; } + @NonNull @Override public String toString() { return CLASS_NAME + "(" + mType + ", " + mComponentID + ", " + mValueId + ")"; @@ -65,7 +70,7 @@ public class ComponentValue implements Operation, SerializableToString { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mType, mComponentID, mValueId); } @@ -74,7 +79,7 @@ public class ComponentValue implements Operation, SerializableToString { // Nothing } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int type = buffer.readInt(); int componentId = buffer.readInt(); int valueId = buffer.readInt(); @@ -82,7 +87,7 @@ public class ComponentValue implements Operation, SerializableToString { operations.add(op); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Expressions Operations", OP_CODE, CLASS_NAME) .description("Encode a component-related value (eg its width, height etc.)") .field( @@ -111,20 +116,21 @@ public class ComponentValue implements Operation, SerializableToString { * @param componentId component id to reference * @param valueId remote float used to represent the component value */ - public static void apply(WireBuffer buffer, int type, int componentId, int valueId) { + public static void apply(@NonNull WireBuffer buffer, int type, int componentId, int valueId) { buffer.start(OP_CODE); buffer.writeInt(type); buffer.writeInt(componentId); buffer.writeInt(valueId); } + @Nullable @Override public String deepToString(String indent) { return null; } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { String type = "WIDTH"; if (mType == HEIGHT) { type = "HEIGHT"; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java index 00758694c254..ba02b91b36ed 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -48,7 +50,7 @@ public class DataListFloat implements VariableSupport, ArrayAccess, Operation { } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { context.addCollection(mId, this); for (float value : mValues) { if (Utils.isVariable(value)) { @@ -58,16 +60,17 @@ public class DataListFloat implements VariableSupport, ArrayAccess, Operation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mId, mValues); } + @NonNull @Override public String toString() { return "DataListFloat[" + Utils.idString(mId) + "] " + Arrays.toString(mValues); } - public static void apply(WireBuffer buffer, int id, float[] values) { + public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] values) { buffer.start(OP_CODE); buffer.writeInt(id); buffer.writeInt(values.length); @@ -76,7 +79,7 @@ public class DataListFloat implements VariableSupport, ArrayAccess, Operation { } } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int id = buffer.readInt(); int len = buffer.readInt(); if (len > MAX_FLOAT_ARRAY) { @@ -90,7 +93,7 @@ public class DataListFloat implements VariableSupport, ArrayAccess, Operation { operations.add(data); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Data Operations", OP_CODE, CLASS_NAME) .description("a list of Floats") .field(DocumentedOperation.INT, "id", "id the array (2xxxxx)") @@ -98,13 +101,14 @@ public class DataListFloat implements VariableSupport, ArrayAccess, Operation { .field(FLOAT_ARRAY, "values", "length", "array of floats"); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.addCollection(mId, this); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java index c43dab4bbee0..b9820f856a92 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -49,16 +51,17 @@ public class DataListIds implements VariableSupport, ArrayAccess, Operation { public void registerListening(RemoteContext context) {} @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mId, mIds); } + @NonNull @Override public String toString() { return "map[" + Utils.idString(mId) + "] \"" + Arrays.toString(mIds) + "\""; } - public static void apply(WireBuffer buffer, int id, int[] ids) { + public static void apply(@NonNull WireBuffer buffer, int id, @NonNull int[] ids) { buffer.start(OP_CODE); buffer.writeInt(id); buffer.writeInt(ids.length); @@ -67,7 +70,7 @@ public class DataListIds implements VariableSupport, ArrayAccess, Operation { } } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int id = buffer.readInt(); int len = buffer.readInt(); if (len > MAX_LIST) { @@ -81,7 +84,7 @@ public class DataListIds implements VariableSupport, ArrayAccess, Operation { operations.add(data); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Data Operations", OP_CODE, CLASS_NAME) .description("a list of id's") .field(DocumentedOperation.INT, "id", "id the array") @@ -89,13 +92,14 @@ public class DataListIds implements VariableSupport, ArrayAccess, Operation { .field(INT_ARRAY, "ids[n]", "length", "ids of other variables"); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.addCollection(mId, this); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java index 75db29d2781e..fb559bbf1863 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.UTF8; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -64,10 +66,11 @@ public class DataMapIds implements Operation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mId, mDataMap.mNames, mDataMap.mTypes, mDataMap.mIds); } + @NonNull @Override public String toString() { StringBuilder builder = new StringBuilder("DataMapIds[" + Utils.idString(mId) + "] "); @@ -84,7 +87,8 @@ public class DataMapIds implements Operation { return builder.toString(); } - public static void apply(WireBuffer buffer, int id, String[] names, byte[] type, int[] ids) { + public static void apply( + @NonNull WireBuffer buffer, int id, @NonNull String[] names, byte[] type, int[] ids) { buffer.start(OP_CODE); buffer.writeInt(id); buffer.writeInt(names.length); @@ -95,7 +99,7 @@ public class DataMapIds implements Operation { } } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int id = buffer.readInt(); int len = buffer.readInt(); if (len > MAX_MAP) { @@ -113,7 +117,7 @@ public class DataMapIds implements Operation { operations.add(data); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Data Operations", OP_CODE, CLASS_NAME) .description("Encode a collection of name id pairs") .field(INT, "id", "id the array") @@ -122,13 +126,14 @@ public class DataMapIds implements Operation { .field(UTF8, "id[0]", "length", "path encoded as floats"); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.putDataMap(mId, mDataMap); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java index e078307f3bd9..629f78647079 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -28,7 +30,7 @@ public class DrawArc extends DrawBase6 { public static final int OP_CODE = Operations.DRAW_ARC; private static final String CLASS_NAME = "DrawArc"; - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Maker m = DrawArc::new; read(m, buffer, operations); } @@ -49,7 +51,13 @@ public class DrawArc extends DrawBase6 { * @param v6 Sweep angle (in degrees) measured clockwise */ public static void apply( - WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) { + @NonNull WireBuffer buffer, + float v1, + float v2, + float v3, + float v4, + float v5, + float v6) { buffer.start(OP_CODE); buffer.writeFloat(v1); buffer.writeFloat(v2); @@ -61,11 +69,17 @@ public class DrawArc extends DrawBase6 { @Override protected void write( - WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) { + @NonNull WireBuffer buffer, + float v1, + float v2, + float v3, + float v4, + float v5, + float v6) { apply(buffer, v1, v2, v3, v4, v5, v6); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description( "Draw the specified arc" @@ -90,7 +104,7 @@ public class DrawArc extends DrawBase6 { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.drawArc(mV1, mV2, mV3, mV4, mV5, mV6); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java index c678cc4a36be..984599e09c19 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java @@ -17,6 +17,9 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.PaintOperation; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -27,7 +30,7 @@ import java.util.List; /** Base class for commands that take 3 float */ public abstract class DrawBase2 extends PaintOperation implements VariableSupport { - protected String mName = "DrawRectBase"; + @NonNull protected String mName = "DrawRectBase"; float mV1; float mV2; float mValue1; @@ -41,13 +44,13 @@ public abstract class DrawBase2 extends PaintOperation implements VariableSuppor } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { mV1 = Float.isNaN(mValue1) ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1; mV2 = Float.isNaN(mValue2) ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2; } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (Float.isNaN(mValue1)) { context.listensTo(Utils.idFromNan(mValue1), this); } @@ -67,12 +70,14 @@ public abstract class DrawBase2 extends PaintOperation implements VariableSuppor DrawBase2 create(float v1, float v2); } + @NonNull @Override public String toString() { return mName + " " + floatToString(mV1) + " " + floatToString(mV2); } - public static void read(Maker maker, WireBuffer buffer, List<Operation> operations) { + public static void read( + @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float v1 = buffer.readFloat(); float v2 = buffer.readFloat(); @@ -87,6 +92,7 @@ public abstract class DrawBase2 extends PaintOperation implements VariableSuppor * @param y1 * @return */ + @Nullable public Operation construct(float x1, float y1) { return null; } @@ -99,7 +105,7 @@ public abstract class DrawBase2 extends PaintOperation implements VariableSuppor * @param x1 * @param y1 */ - protected static void write(WireBuffer buffer, int opCode, float x1, float y1) { + protected static void write(@NonNull WireBuffer buffer, int opCode, float x1, float y1) { buffer.start(opCode); buffer.writeFloat(x1); buffer.writeFloat(y1); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java index e1108e906d8d..825da5240032 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java @@ -17,6 +17,9 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.PaintOperation; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -28,7 +31,7 @@ import java.util.List; /** Base class for commands that take 3 float */ public abstract class DrawBase3 extends PaintOperation implements VariableSupport { - protected String mName = "DrawRectBase"; + @NonNull protected String mName = "DrawRectBase"; float mV1; float mV2; float mV3; @@ -47,14 +50,14 @@ public abstract class DrawBase3 extends PaintOperation implements VariableSuppor } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { mV1 = Utils.isVariable(mValue1) ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1; mV2 = Utils.isVariable(mValue2) ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2; mV3 = Utils.isVariable(mValue3) ? context.getFloat(Utils.idFromNan(mValue3)) : mValue3; } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (Utils.isVariable(mValue1)) { context.listensTo(Utils.idFromNan(mValue1), this); } @@ -77,6 +80,7 @@ public abstract class DrawBase3 extends PaintOperation implements VariableSuppor DrawBase3 create(float v1, float v2, float v3); } + @NonNull @Override public String toString() { return mName @@ -88,7 +92,8 @@ public abstract class DrawBase3 extends PaintOperation implements VariableSuppor + floatToString(mV3); } - public static void read(Maker maker, WireBuffer buffer, List<Operation> operations) { + public static void read( + @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float v1 = buffer.readFloat(); float v2 = buffer.readFloat(); float v3 = buffer.readFloat(); @@ -104,6 +109,7 @@ public abstract class DrawBase3 extends PaintOperation implements VariableSuppor * @param x2 * @return */ + @Nullable public Operation construct(float x1, float y1, float x2) { return null; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java index 09f0df985b5c..a23bcb9dca78 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java @@ -17,6 +17,9 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.PaintOperation; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -27,7 +30,7 @@ import java.util.List; /** Base class for draw commands that take 4 floats */ public abstract class DrawBase4 extends PaintOperation implements VariableSupport { - protected String mName = "DrawRectBase"; + @NonNull protected String mName = "DrawRectBase"; protected float mX1; protected float mY1; protected float mX2; @@ -50,7 +53,7 @@ public abstract class DrawBase4 extends PaintOperation implements VariableSuppor } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { mX1 = Float.isNaN(mX1Value) ? context.getFloat(Utils.idFromNan(mX1Value)) : mX1Value; mY1 = Float.isNaN(mY1Value) ? context.getFloat(Utils.idFromNan(mY1Value)) : mY1Value; mX2 = Float.isNaN(mX2Value) ? context.getFloat(Utils.idFromNan(mX2Value)) : mX2Value; @@ -58,7 +61,7 @@ public abstract class DrawBase4 extends PaintOperation implements VariableSuppor } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (Float.isNaN(mX1Value)) { context.listensTo(Utils.idFromNan(mX1Value), this); } @@ -84,6 +87,7 @@ public abstract class DrawBase4 extends PaintOperation implements VariableSuppor DrawBase4 create(float v1, float v2, float v3, float v4); } + @NonNull @Override public String toString() { return mName @@ -97,7 +101,8 @@ public abstract class DrawBase4 extends PaintOperation implements VariableSuppor + floatToString(mY2Value, mY2); } - public static void read(Maker maker, WireBuffer buffer, List<Operation> operations) { + public static void read( + @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float v1 = buffer.readFloat(); float v2 = buffer.readFloat(); float v3 = buffer.readFloat(); @@ -116,6 +121,7 @@ public abstract class DrawBase4 extends PaintOperation implements VariableSuppor * @param y2 * @return */ + @Nullable public Operation construct(float x1, float y1, float x2, float y2) { return null; } @@ -131,7 +137,7 @@ public abstract class DrawBase4 extends PaintOperation implements VariableSuppor * @param y2 */ protected static void write( - WireBuffer buffer, int opCode, float x1, float y1, float x2, float y2) { + @NonNull WireBuffer buffer, int opCode, float x1, float y1, float x2, float y2) { buffer.start(opCode); buffer.writeFloat(x1); buffer.writeFloat(y1); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java index e071d5f2096f..68c9f8cdc462 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.PaintOperation; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -25,7 +28,7 @@ import java.util.List; /** Base class for draw commands the take 6 floats */ public abstract class DrawBase6 extends PaintOperation implements VariableSupport { - protected String mName = "DrawRectBase"; + @NonNull protected String mName = "DrawRectBase"; float mV1; float mV2; float mV3; @@ -56,7 +59,7 @@ public abstract class DrawBase6 extends PaintOperation implements VariableSuppor } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { mV1 = Float.isNaN(mValue1) ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1; mV2 = Float.isNaN(mValue2) ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2; mV3 = Float.isNaN(mValue3) ? context.getFloat(Utils.idFromNan(mValue3)) : mValue3; @@ -66,7 +69,7 @@ public abstract class DrawBase6 extends PaintOperation implements VariableSuppor } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (Float.isNaN(mValue1)) { context.listensTo(Utils.idFromNan(mValue1), this); } @@ -95,6 +98,7 @@ public abstract class DrawBase6 extends PaintOperation implements VariableSuppor protected abstract void write( WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6); + @NonNull @Override public String toString() { return mName @@ -112,7 +116,8 @@ public abstract class DrawBase6 extends PaintOperation implements VariableSuppor DrawBase6 create(float v1, float v2, float v3, float v4, float v5, float v6); } - public static void read(Maker build, WireBuffer buffer, List<Operation> operations) { + public static void read( + @NonNull Maker build, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float sv1 = buffer.readFloat(); float sv2 = buffer.readFloat(); float sv3 = buffer.readFloat(); @@ -135,10 +140,12 @@ public abstract class DrawBase6 extends PaintOperation implements VariableSuppor * @param v6 * @return */ + @Nullable public Operation construct(float v1, float v2, float v3, float v4, float v5, float v6) { return null; } + @NonNull public static String name() { return "DrawBase6"; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java index 0b43fd24556a..9c23c9559953 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -54,7 +56,7 @@ public class DrawBitmap extends PaintOperation implements VariableSupport { } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { mOutputLeft = Float.isNaN(mLeft) ? context.getFloat(Utils.idFromNan(mLeft)) : mLeft; mOutputTop = Float.isNaN(mTop) ? context.getFloat(Utils.idFromNan(mTop)) : mTop; mOutputRight = Float.isNaN(mRight) ? context.getFloat(Utils.idFromNan(mRight)) : mRight; @@ -62,7 +64,7 @@ public class DrawBitmap extends PaintOperation implements VariableSupport { } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (Float.isNaN(mLeft)) { context.listensTo(Utils.idFromNan(mLeft), this); } @@ -78,10 +80,11 @@ public class DrawBitmap extends PaintOperation implements VariableSupport { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mId, mLeft, mTop, mRight, mBottom, mDescriptionId); } + @NonNull @Override public String toString() { return "DrawBitmap (desc=" @@ -97,7 +100,7 @@ public class DrawBitmap extends PaintOperation implements VariableSupport { + ";"; } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int id = buffer.readInt(); float sLeft = buffer.readFloat(); float srcTop = buffer.readFloat(); @@ -109,6 +112,7 @@ public class DrawBitmap extends PaintOperation implements VariableSupport { operations.add(op); } + @NonNull public static String name() { return CLASS_NAME; } @@ -118,7 +122,7 @@ public class DrawBitmap extends PaintOperation implements VariableSupport { } public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, int id, float left, float top, @@ -134,7 +138,7 @@ public class DrawBitmap extends PaintOperation implements VariableSupport { buffer.writeInt(descriptionId); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Draw Operations", OP_CODE, CLASS_NAME) .description("Draw a bitmap") .field(INT, "id", "id of float") @@ -146,7 +150,7 @@ public class DrawBitmap extends PaintOperation implements VariableSupport { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.drawBitmap(mId, mOutputLeft, mOutputTop, mOutputRight, mOutputBottom); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java index fc7482759369..da9fe247bced 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -64,7 +66,7 @@ public class DrawBitmapInt extends PaintOperation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply( buffer, mImageId, @@ -79,6 +81,7 @@ public class DrawBitmapInt extends PaintOperation { mContentDescId); } + @NonNull @Override public String toString() { return "DRAW_BITMAP_INT " @@ -103,6 +106,7 @@ public class DrawBitmapInt extends PaintOperation { + ";"; } + @NonNull public static String name() { return CLASS_NAME; } @@ -112,7 +116,7 @@ public class DrawBitmapInt extends PaintOperation { } public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, int imageId, int srcLeft, int srcTop, @@ -136,7 +140,7 @@ public class DrawBitmapInt extends PaintOperation { buffer.writeInt(cdId); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int imageId = buffer.readInt(); int sLeft = buffer.readInt(); int srcTop = buffer.readInt(); @@ -155,7 +159,7 @@ public class DrawBitmapInt extends PaintOperation { operations.add(op); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Draw Operations", OP_CODE, CLASS_NAME) .description("Draw a bitmap using integer coordinates") .field(DocumentedOperation.INT, "id", "id of bitmap") @@ -171,7 +175,7 @@ public class DrawBitmapInt extends PaintOperation { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.drawBitmap( mImageId, mSrcLeft, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java index 22742c63dd2d..e20bcd2d79b5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -45,7 +47,7 @@ public class DrawBitmapScaled extends PaintOperation implements VariableSupport float mScaleFactor, mOutScaleFactor; int mScaleType; - ImageScaling mScaling = new ImageScaling(); + @NonNull ImageScaling mScaling = new ImageScaling(); public static final int SCALE_NONE = ImageScaling.SCALE_NONE; public static final int SCALE_INSIDE = ImageScaling.SCALE_INSIDE; public static final int SCALE_FILL_WIDTH = ImageScaling.SCALE_FILL_WIDTH; @@ -83,7 +85,7 @@ public class DrawBitmapScaled extends PaintOperation implements VariableSupport } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { mOutSrcLeft = Float.isNaN(mSrcLeft) ? context.getFloat(Utils.idFromNan(mSrcLeft)) : mSrcLeft; mOutSrcTop = Float.isNaN(mSrcTop) ? context.getFloat(Utils.idFromNan(mSrcTop)) : mSrcTop; @@ -109,7 +111,7 @@ public class DrawBitmapScaled extends PaintOperation implements VariableSupport } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { register(context, mSrcLeft); register(context, mSrcTop); register(context, mSrcRight); @@ -121,12 +123,13 @@ public class DrawBitmapScaled extends PaintOperation implements VariableSupport register(context, mScaleFactor); } - private void register(RemoteContext context, float value) { + private void register(@NonNull RemoteContext context, float value) { if (Float.isNaN(value)) { context.listensTo(Utils.idFromNan(value), this); } } + @NonNull static String str(float v) { String s = " " + (int) v; return s.substring(s.length() - 3); @@ -140,7 +143,7 @@ public class DrawBitmapScaled extends PaintOperation implements VariableSupport } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply( buffer, mImageId, @@ -157,6 +160,7 @@ public class DrawBitmapScaled extends PaintOperation implements VariableSupport mContentDescId); } + @NonNull @Override public String toString() { return "DrawBitmapScaled " @@ -185,6 +189,7 @@ public class DrawBitmapScaled extends PaintOperation implements VariableSupport + Utils.floatToString(mScaleFactor, mOutScaleFactor); } + @NonNull public static String name() { return CLASS_NAME; } @@ -194,7 +199,7 @@ public class DrawBitmapScaled extends PaintOperation implements VariableSupport } public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, int imageId, float srcLeft, float srcTop, @@ -225,7 +230,7 @@ public class DrawBitmapScaled extends PaintOperation implements VariableSupport buffer.writeInt(cdId); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int imageId = buffer.readInt(); float sLeft = buffer.readFloat(); @@ -258,7 +263,7 @@ public class DrawBitmapScaled extends PaintOperation implements VariableSupport operations.add(op); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Draw Operations", OP_CODE, CLASS_NAME) .description("Draw a bitmap using integer coordinates") .field(DocumentedOperation.INT, "id", "id of bitmap") @@ -289,7 +294,7 @@ public class DrawBitmapScaled extends PaintOperation implements VariableSupport // } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { mScaling.setup( mOutSrcLeft, mOutSrcTop, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java index 04f095af29dc..11bd49a4a651 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -28,7 +30,7 @@ public class DrawCircle extends DrawBase3 { private static final int OP_CODE = Operations.DRAW_CIRCLE; private static final String CLASS_NAME = "DrawCircle"; - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Maker m = DrawCircle::new; read(m, buffer, operations); } @@ -37,11 +39,12 @@ public class DrawCircle extends DrawBase3 { return OP_CODE; } + @NonNull public static String name() { return CLASS_NAME; } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description("Draw a Circle") .field( @@ -56,7 +59,7 @@ public class DrawCircle extends DrawBase3 { } @Override - protected void write(WireBuffer buffer, float v1, float v2, float v3) { + protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3) { apply(buffer, v1, v2, v3); } @@ -66,7 +69,7 @@ public class DrawCircle extends DrawBase3 { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.drawCircle(mV1, mV2, mV3); } @@ -78,7 +81,7 @@ public class DrawCircle extends DrawBase3 { * @param y1 * @param x2 */ - public static void apply(WireBuffer buffer, float x1, float y1, float x2) { + public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2) { buffer.start(OP_CODE); buffer.writeFloat(x1); buffer.writeFloat(y1); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java index dacbb0361761..7310a9defba6 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -30,7 +32,7 @@ public class DrawLine extends DrawBase4 implements SerializableToString { private static final int OP_CODE = Operations.DRAW_LINE; private static final String CLASS_NAME = "DrawLine"; - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Maker m = DrawLine::new; read(m, buffer, operations); } @@ -39,11 +41,12 @@ public class DrawLine extends DrawBase4 implements SerializableToString { return OP_CODE; } + @NonNull public static String name() { return CLASS_NAME; } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description("Draw a line segment") .field( @@ -65,7 +68,7 @@ public class DrawLine extends DrawBase4 implements SerializableToString { } @Override - protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) { + protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) { apply(buffer, v1, v2, v3, v4); } @@ -75,7 +78,7 @@ public class DrawLine extends DrawBase4 implements SerializableToString { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.drawLine(mX1, mY1, mX2, mY2); } @@ -88,12 +91,12 @@ public class DrawLine extends DrawBase4 implements SerializableToString { * @param x2 end x of the line * @param y2 end y of the line */ - public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) { + public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) { write(buffer, OP_CODE, x1, y1, x2, y2); } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { String x1 = "" + mX1; if (Float.isNaN(mX1Value)) { x1 = "[" + Utils.idFromNan(mX1Value) + " = " + mX1 + "]"; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java index 5d498e81c9d2..aa5116e950c5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -28,7 +30,7 @@ public class DrawOval extends DrawBase4 { private static final int OP_CODE = Operations.DRAW_OVAL; private static final String CLASS_NAME = "DrawOval"; - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Maker m = DrawOval::new; read(m, buffer, operations); } @@ -37,11 +39,12 @@ public class DrawOval extends DrawBase4 { return OP_CODE; } + @NonNull public static String name() { return CLASS_NAME; } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description("Draw the specified oval") .field(DocumentedOperation.FLOAT, "left", "The left side of the oval") @@ -51,12 +54,12 @@ public class DrawOval extends DrawBase4 { } @Override - protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) { + protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) { apply(buffer, v1, v2, v3, v4); } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mX1, mY1, mX2, mY2); } @@ -66,7 +69,7 @@ public class DrawOval extends DrawBase4 { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.drawOval(mX1, mY1, mX2, mY2); } @@ -79,7 +82,7 @@ public class DrawOval extends DrawBase4 { * @param x2 end x of the DrawOval * @param y2 end y of the DrawOval */ - public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) { + public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) { write(buffer, OP_CODE, x1, y1, x2, y2); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java index ccbf3d9e3091..d35094b0b351 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -38,21 +40,23 @@ public class DrawPath extends PaintOperation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mId); } + @NonNull @Override public String toString() { return "DrawPath " + "[" + mId + "]" + ", " + mStart + ", " + mEnd; } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int id = buffer.readInt(); DrawPath op = new DrawPath(id); operations.add(op); } + @NonNull public static String name() { return CLASS_NAME; } @@ -61,19 +65,19 @@ public class DrawPath extends PaintOperation { return Operations.DRAW_PATH; } - public static void apply(WireBuffer buffer, int id) { + public static void apply(@NonNull WireBuffer buffer, int id) { buffer.start(Operations.DRAW_PATH); buffer.writeInt(id); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Draw Operations", OP_CODE, CLASS_NAME) .description("Draw a bitmap using integer coordinates") .field(DocumentedOperation.INT, "id", "id of path"); } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.drawPath(mId, mStart, mEnd); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java index 644011b8a214..db7633cbe335 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -29,7 +31,7 @@ public class DrawRect extends DrawBase4 { private static final int OP_CODE = Operations.DRAW_RECT; private static final String CLASS_NAME = "DrawRect"; - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Maker m = DrawRect::new; read(m, buffer, operations); } @@ -38,11 +40,12 @@ public class DrawRect extends DrawBase4 { return OP_CODE; } + @NonNull public static String name() { return CLASS_NAME; } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description("Draw the specified rectangle") .field(DocumentedOperation.FLOAT, "left", "The left side of the rectangle") @@ -52,7 +55,7 @@ public class DrawRect extends DrawBase4 { } @Override - protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) { + protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) { apply(buffer, v1, v2, v3, v4); } @@ -62,7 +65,7 @@ public class DrawRect extends DrawBase4 { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.drawRect(mX1, mY1, mX2, mY2); } @@ -75,7 +78,7 @@ public class DrawRect extends DrawBase4 { * @param x2 right x of the rect * @param y2 bottom y of the rect */ - public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) { + public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) { write(buffer, OP_CODE, x1, y1, x2, y2); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java index 64a3b283505b..c306e2b5f041 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -29,7 +31,7 @@ public class DrawRoundRect extends DrawBase6 { private static final int OP_CODE = Operations.DRAW_ROUND_RECT; private static final String CLASS_NAME = "DrawRoundRect"; - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Maker m = DrawRoundRect::new; read(m, buffer, operations); } @@ -50,7 +52,13 @@ public class DrawRoundRect extends DrawBase6 { * @param v6 The y-radius of the oval used to round the corners */ public static void apply( - WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) { + @NonNull WireBuffer buffer, + float v1, + float v2, + float v3, + float v4, + float v5, + float v6) { buffer.start(OP_CODE); buffer.writeFloat(v1); buffer.writeFloat(v2); @@ -62,11 +70,17 @@ public class DrawRoundRect extends DrawBase6 { @Override protected void write( - WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) { + @NonNull WireBuffer buffer, + float v1, + float v2, + float v3, + float v4, + float v5, + float v6) { apply(buffer, v1, v2, v3, v4, v5, v6); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description("Draw the specified round-rect") .field(DocumentedOperation.FLOAT, "left", "The left side of the rect") @@ -89,7 +103,7 @@ public class DrawRoundRect extends DrawBase6 { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.drawRoundRect(mV1, mV2, mV3, mV4, mV5, mV6); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java index 3cb191647c33..3b60df7d529e 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -28,7 +30,7 @@ public class DrawSector extends DrawBase6 { public static final int OP_CODE = Operations.DRAW_SECTOR; private static final String CLASS_NAME = "DrawSector"; - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Maker m = DrawSector::new; read(m, buffer, operations); } @@ -49,7 +51,13 @@ public class DrawSector extends DrawBase6 { * @param v6 Sweep angle (in degrees) measured clockwise */ public static void apply( - WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) { + @NonNull WireBuffer buffer, + float v1, + float v2, + float v3, + float v4, + float v5, + float v6) { buffer.start(OP_CODE); buffer.writeFloat(v1); buffer.writeFloat(v2); @@ -61,11 +69,17 @@ public class DrawSector extends DrawBase6 { @Override protected void write( - WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) { + @NonNull WireBuffer buffer, + float v1, + float v2, + float v3, + float v4, + float v5, + float v6) { apply(buffer, v1, v2, v3, v4, v5, v6); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description( "Draw the specified sector (pie shape)" @@ -90,7 +104,7 @@ public class DrawSector extends DrawBase6 { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.drawSector(mV1, mV2, mV3, mV4, mV5, mV6); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java index bcb7852e6615..9c587aba3f7b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -64,13 +66,13 @@ public class DrawText extends PaintOperation implements VariableSupport { } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { mOutX = Float.isNaN(mX) ? context.getFloat(Utils.idFromNan(mX)) : mX; mOutY = Float.isNaN(mY) ? context.getFloat(Utils.idFromNan(mY)) : mY; } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (Float.isNaN(mX)) { context.listensTo(Utils.idFromNan(mX), this); } @@ -80,10 +82,11 @@ public class DrawText extends PaintOperation implements VariableSupport { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl); } + @NonNull @Override public String toString() { return "DrawTextRun [" @@ -98,7 +101,7 @@ public class DrawText extends PaintOperation implements VariableSupport { + floatToString(mY, mOutY); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int text = buffer.readInt(); int start = buffer.readInt(); int end = buffer.readInt(); @@ -112,6 +115,7 @@ public class DrawText extends PaintOperation implements VariableSupport { operations.add(op); } + @NonNull public static String name() { return CLASS_NAME; } @@ -134,7 +138,7 @@ public class DrawText extends PaintOperation implements VariableSupport { * @param rtl is it Right to Left text */ public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, int textID, int start, int end, @@ -154,7 +158,7 @@ public class DrawText extends PaintOperation implements VariableSupport { buffer.writeBoolean(rtl); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Draw Operations", id(), CLASS_NAME) .description("Draw a run of text, all in a single direction") .field(DocumentedOperation.INT, "textId", "id of bitmap") @@ -177,7 +181,7 @@ public class DrawText extends PaintOperation implements VariableSupport { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.drawTextRun(mTextID, mStart, mEnd, mContextStart, mContextEnd, mOutX, mOutY, mRtl); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java index 95a87667dfab..8b7018191f4d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -57,7 +59,7 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { mOutX = Float.isNaN(mX) ? context.getFloat(Utils.idFromNan(mX)) : mX; mOutY = Float.isNaN(mY) ? context.getFloat(Utils.idFromNan(mY)) : mY; mOutPanX = Float.isNaN(mPanX) ? context.getFloat(Utils.idFromNan(mPanX)) : mPanX; @@ -65,7 +67,7 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (Float.isNaN(mX)) { context.listensTo(Utils.idFromNan(mX), this); } @@ -81,10 +83,11 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mTextID, mX, mY, mPanX, mPanY, mFlags); } + @NonNull @Override public String toString() { return "DrawTextAnchored [" @@ -108,7 +111,7 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport return Float.toString(v); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int textID = buffer.readInt(); float x = buffer.readFloat(); float y = buffer.readFloat(); @@ -121,6 +124,7 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport operations.add(op); } + @NonNull public static String name() { return CLASS_NAME; } @@ -141,7 +145,13 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport * @param flags Change the behaviour */ public static void apply( - WireBuffer buffer, int textID, float x, float y, float panX, float panY, int flags) { + @NonNull WireBuffer buffer, + int textID, + float x, + float y, + float panX, + float panY, + int flags) { buffer.start(OP_CODE); buffer.writeInt(textID); buffer.writeFloat(x); @@ -151,7 +161,7 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport buffer.writeInt(flags); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Draw Operations", OP_CODE, CLASS_NAME) .description("Draw text centered about an anchor point") .field(DocumentedOperation.INT, "textId", "id of bitmap") @@ -168,7 +178,7 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport .field(DocumentedOperation.INT, "flags", "Change the behaviour"); } - float[] mBounds = new float[4]; + @NonNull float[] mBounds = new float[4]; private float getHorizontalOffset() { // TODO scale TextSize / BaseTextSize; @@ -188,7 +198,7 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { int flags = ((mFlags & ANCHOR_MONOSPACE_MEASURE) != 0) ? PaintContext.TEXT_MEASURE_MONOSPACE_WIDTH diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java index aefd6f397ebf..e90122bb95ac 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -46,7 +48,7 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport { } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { mOutHOffset = Float.isNaN(mHOffset) ? context.getFloat(Utils.idFromNan(mHOffset)) : mHOffset; mOutVOffset = @@ -54,7 +56,7 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport { } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (Float.isNaN(mHOffset)) { context.listensTo(Utils.idFromNan(mHOffset), this); } @@ -64,10 +66,11 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mTextId, mPathId, mHOffset, mVOffset); } + @NonNull @Override public String toString() { return "DrawTextOnPath [" @@ -80,7 +83,7 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport { + Utils.floatToString(mVOffset, mOutVOffset); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int textId = buffer.readInt(); int pathId = buffer.readInt(); float vOffset = buffer.readFloat(); @@ -89,6 +92,7 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport { operations.add(op); } + @NonNull public static String name() { return "DrawTextOnPath"; } @@ -98,7 +102,7 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport { } public static void apply( - WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) { + @NonNull WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) { buffer.start(OP_CODE); buffer.writeInt(textId); buffer.writeInt(pathId); @@ -106,7 +110,7 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport { buffer.writeFloat(hOffset); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Draw Operations", OP_CODE, CLASS_NAME) .description("Draw text along path object") .field(DocumentedOperation.INT, "textId", "id of the text") @@ -116,7 +120,7 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.drawTextOnPath(mTextId, mPathId, mOutHOffset, mOutVOffset); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java index b6d45d95f2c0..0aaaf42ba838 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -50,14 +52,14 @@ public class DrawTweenPath extends PaintOperation implements VariableSupport { } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { mOutTween = Float.isNaN(mTween) ? context.getFloat(Utils.idFromNan(mTween)) : mTween; mOutStart = Float.isNaN(mStart) ? context.getFloat(Utils.idFromNan(mStart)) : mStart; mOutStop = Float.isNaN(mStop) ? context.getFloat(Utils.idFromNan(mStop)) : mStop; } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (Float.isNaN(mTween)) { context.listensTo(Utils.idFromNan(mTween), this); } @@ -70,10 +72,11 @@ public class DrawTweenPath extends PaintOperation implements VariableSupport { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mPath1Id, mPath2Id, mTween, mStart, mStop); } + @NonNull @Override public String toString() { return "DrawTweenPath " @@ -89,7 +92,7 @@ public class DrawTweenPath extends PaintOperation implements VariableSupport { + floatToString(mStop, mOutStop); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int path1Id = buffer.readInt(); int path2Id = buffer.readInt(); float tween = buffer.readFloat(); @@ -99,6 +102,7 @@ public class DrawTweenPath extends PaintOperation implements VariableSupport { operations.add(op); } + @NonNull public static String name() { return "DrawTweenPath"; } @@ -108,7 +112,12 @@ public class DrawTweenPath extends PaintOperation implements VariableSupport { } public static void apply( - WireBuffer buffer, int path1Id, int path2Id, float tween, float start, float stop) { + @NonNull WireBuffer buffer, + int path1Id, + int path2Id, + float tween, + float start, + float stop) { buffer.start(OP_CODE); buffer.writeInt(path1Id); buffer.writeInt(path2Id); @@ -117,7 +126,7 @@ public class DrawTweenPath extends PaintOperation implements VariableSupport { buffer.writeFloat(stop); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Draw Operations", OP_CODE, CLASS_NAME) .description("Draw text along path object") .field(DocumentedOperation.INT, "pathId1", "id of path 1") @@ -128,7 +137,7 @@ public class DrawTweenPath extends PaintOperation implements VariableSupport { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.drawTweenPath(mPath1Id, mPath2Id, mOutTween, mOutStart, mOutStop); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java index 765e150e81af..89390acb8c50 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -39,15 +41,17 @@ public class FloatConstant implements com.android.internal.widget.remotecompose. } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mTextId, mValue); } + @NonNull @Override public String toString() { return "FloatConstant[" + mTextId + "] = " + mValue; } + @NonNull public static String name() { return CLASS_NAME; } @@ -63,20 +67,20 @@ public class FloatConstant implements com.android.internal.widget.remotecompose. * @param id the id * @param value the value of the float */ - public static void apply(WireBuffer buffer, int id, float value) { + public static void apply(@NonNull WireBuffer buffer, int id, float value) { buffer.start(OP_CODE); buffer.writeInt(id); buffer.writeFloat(value); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int textId = buffer.readInt(); float value = buffer.readFloat(); operations.add(new FloatConstant(textId, value)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Expressions Operations", OP_CODE, CLASS_NAME) .description("A float and its associated id") .field(DocumentedOperation.INT, "id", "id of float") @@ -84,10 +88,11 @@ public class FloatConstant implements com.android.internal.widget.remotecompose. } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.loadFloat(mTextId, mValue); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java index d71793364a33..e1c6c2577860 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java @@ -20,6 +20,9 @@ import static com.android.internal.widget.remotecompose.core.documentation.Docum import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -48,7 +51,7 @@ public class FloatExpression implements Operation, VariableSupport { public float[] mPreCalcValue; private float mLastChange = Float.NaN; private float mLastCalculatedValue = Float.NaN; - AnimatedFloatExpression mExp = new AnimatedFloatExpression(); + @NonNull AnimatedFloatExpression mExp = new AnimatedFloatExpression(); public static final int MAX_EXPRESSION_SIZE = 32; public FloatExpression(int id, float[] value, float[] animation) { @@ -61,7 +64,7 @@ public class FloatExpression implements Operation, VariableSupport { } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { if (mPreCalcValue == null || mPreCalcValue.length != mSrcValue.length) { mPreCalcValue = new float[mSrcValue.length]; } @@ -107,7 +110,7 @@ public class FloatExpression implements Operation, VariableSupport { } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { for (float v : mSrcValue) { if (Float.isNaN(v) && !AnimatedFloatExpression.isMathOperator(v) @@ -118,7 +121,7 @@ public class FloatExpression implements Operation, VariableSupport { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { updateVariables(context); float t = context.getAnimationTime(); if (Float.isNaN(mLastChange)) { @@ -135,10 +138,11 @@ public class FloatExpression implements Operation, VariableSupport { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mId, mSrcValue, mSrcAnimation); } + @NonNull @Override public String toString() { String[] labels = new String[mSrcValue.length]; @@ -161,6 +165,7 @@ public class FloatExpression implements Operation, VariableSupport { + ")"; } + @NonNull public static String name() { return CLASS_NAME; } @@ -177,7 +182,11 @@ public class FloatExpression implements Operation, VariableSupport { * @param value the float expression array * @param animation the animation expression array */ - public static void apply(WireBuffer buffer, int id, float[] value, float[] animation) { + public static void apply( + @NonNull WireBuffer buffer, + int id, + @NonNull float[] value, + @Nullable float[] animation) { buffer.start(OP_CODE); buffer.writeInt(id); @@ -197,7 +206,7 @@ public class FloatExpression implements Operation, VariableSupport { } } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int id = buffer.readInt(); int len = buffer.readInt(); int valueLen = len & 0xFFFF; @@ -222,7 +231,7 @@ public class FloatExpression implements Operation, VariableSupport { operations.add(new FloatExpression(id, values, animation)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Expressions Operations", OP_CODE, CLASS_NAME) .description("A Float expression") .field(DocumentedOperation.INT, "id", "The id of the Color") @@ -245,6 +254,7 @@ public class FloatExpression implements Operation, VariableSupport { .field(FLOAT, "wrapValue", "> [Wrap value] "); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java index 4f8516f5235d..1979bc5f4331 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.LONG; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteComposeOperation; @@ -80,10 +82,11 @@ public class Header implements RemoteComposeOperation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mWidth, mHeight, mDensity, mCapabilities); } + @NonNull @Override public String toString() { return "HEADER v" @@ -102,15 +105,17 @@ public class Header implements RemoteComposeOperation { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.header(mMajorVersion, mMinorVersion, mPatchVersion, mWidth, mHeight, mCapabilities); } + @NonNull @Override public String deepToString(String indent) { return toString(); } + @NonNull public static String name() { return CLASS_NAME; } @@ -120,7 +125,7 @@ public class Header implements RemoteComposeOperation { } public static void apply( - WireBuffer buffer, int width, int height, float density, long capabilities) { + @NonNull WireBuffer buffer, int width, int height, float density, long capabilities) { buffer.start(OP_CODE); buffer.writeInt(MAJOR_VERSION); // major version number of the protocol buffer.writeInt(MINOR_VERSION); // minor version number of the protocol @@ -131,7 +136,7 @@ public class Header implements RemoteComposeOperation { buffer.writeLong(capabilities); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int majorVersion = buffer.readInt(); int minorVersion = buffer.readInt(); int patchVersion = buffer.readInt(); @@ -152,7 +157,7 @@ public class Header implements RemoteComposeOperation { operations.add(header); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Protocol Operations", OP_CODE, CLASS_NAME) .description( "Document metadata, containing the version," diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java index c9a850875011..6375f001c818 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -45,7 +47,7 @@ public class IntegerExpression implements Operation, VariableSupport { public int[] mPreCalcValue; private float mLastChange = Float.NaN; public static final int MAX_SIZE = 320; - IntegerExpressionEvaluator mExp = new IntegerExpressionEvaluator(); + @NonNull IntegerExpressionEvaluator mExp = new IntegerExpressionEvaluator(); public IntegerExpression(int id, int mask, int[] value) { this.mId = id; @@ -54,7 +56,7 @@ public class IntegerExpression implements Operation, VariableSupport { } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { if (mPreCalcValue == null || mPreCalcValue.length != mSrcValue.length) { mPreCalcValue = new int[mSrcValue.length]; } @@ -70,7 +72,7 @@ public class IntegerExpression implements Operation, VariableSupport { } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { for (int i = 0; i < mSrcValue.length; i++) { if (isId(mMask, i, mSrcValue[i])) { context.listensTo(mSrcValue[i], this); @@ -79,7 +81,7 @@ public class IntegerExpression implements Operation, VariableSupport { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { updateVariables(context); float t = context.getAnimationTime(); if (Float.isNaN(mLastChange)) { @@ -95,7 +97,7 @@ public class IntegerExpression implements Operation, VariableSupport { * @param context current context * @return the resulting value */ - public int evaluate(RemoteContext context) { + public int evaluate(@NonNull RemoteContext context) { updateVariables(context); float t = context.getAnimationTime(); if (Float.isNaN(mLastChange)) { @@ -105,10 +107,11 @@ public class IntegerExpression implements Operation, VariableSupport { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mId, mMask, mSrcValue); } + @NonNull @Override public String toString() { StringBuilder s = new StringBuilder(); @@ -132,6 +135,7 @@ public class IntegerExpression implements Operation, VariableSupport { return "IntegerExpression[" + mId + "] = (" + s + ")"; } + @NonNull public static String name() { return CLASS_NAME; } @@ -148,7 +152,7 @@ public class IntegerExpression implements Operation, VariableSupport { * @param mask the mask bits of ints & operators or variables * @param value array of integers to be evaluated */ - public static void apply(WireBuffer buffer, int id, int mask, int[] value) { + public static void apply(@NonNull WireBuffer buffer, int id, int mask, @NonNull int[] value) { buffer.start(OP_CODE); buffer.writeInt(id); buffer.writeInt(mask); @@ -158,7 +162,7 @@ public class IntegerExpression implements Operation, VariableSupport { } } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int id = buffer.readInt(); int mask = buffer.readInt(); int len = buffer.readInt(); @@ -173,7 +177,7 @@ public class IntegerExpression implements Operation, VariableSupport { operations.add(new IntegerExpression(id, mask, values)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Data Operations", OP_CODE, CLASS_NAME) .description("Expression that computes an integer") .field(DocumentedOperation.INT, "id", "id of integer") @@ -182,6 +186,7 @@ public class IntegerExpression implements Operation, VariableSupport { .field(INT_ARRAY, "values", "length", "Array of ints"); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java index 04f8a503adff..6a620e58570a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -31,20 +33,22 @@ public class MatrixRestore extends PaintOperation { public MatrixRestore() {} @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(WireBuffer buffer, @NonNull List<Operation> operations) { MatrixRestore op = new MatrixRestore(); operations.add(op); } + @NonNull @Override public String toString() { return "MatrixRestore"; } + @NonNull public static String name() { return CLASS_NAME; } @@ -53,17 +57,17 @@ public class MatrixRestore extends PaintOperation { return OP_CODE; } - public static void apply(WireBuffer buffer) { + public static void apply(@NonNull WireBuffer buffer) { buffer.start(OP_CODE); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description("Restore the matrix and clip"); } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.matrixRestore(); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java index df10f329630a..438a2aad648a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -28,9 +30,10 @@ public class MatrixRotate extends DrawBase3 { public static final int OP_CODE = Operations.MATRIX_ROTATE; private static final String CLASS_NAME = "MatrixRotate"; - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Maker m = new Maker() { + @NonNull @Override public DrawBase3 create(float v1, float v2, float v3) { return new MatrixRotate(v1, v2, v3); @@ -43,11 +46,12 @@ public class MatrixRotate extends DrawBase3 { return OP_CODE; } + @NonNull public static String name() { return CLASS_NAME; } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description("apply rotation to matrix") .field(DocumentedOperation.FLOAT, "rotate", "Angle to rotate") @@ -56,7 +60,7 @@ public class MatrixRotate extends DrawBase3 { } @Override - protected void write(WireBuffer buffer, float v1, float v2, float v3) { + protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3) { apply(buffer, v1, v2, v3); } @@ -66,7 +70,7 @@ public class MatrixRotate extends DrawBase3 { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.matrixRotate(mV1, mV2, mV3); } @@ -78,7 +82,7 @@ public class MatrixRotate extends DrawBase3 { * @param y1 X Pivot point * @param x2 Y Pivot point */ - public static void apply(WireBuffer buffer, float x1, float y1, float x2) { + public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2) { buffer.start(OP_CODE); buffer.writeFloat(x1); buffer.writeFloat(y1); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java index 67612c7fd2f4..1880b19be1a7 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -29,20 +31,22 @@ public class MatrixSave extends PaintOperation { private static final String CLASS_NAME = "MatrixSave"; @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer); } + @NonNull @Override public String toString() { return "MatrixSave;"; } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(WireBuffer buffer, @NonNull List<Operation> operations) { MatrixSave op = new MatrixSave(); operations.add(op); } + @NonNull public static String name() { return CLASS_NAME; } @@ -51,17 +55,17 @@ public class MatrixSave extends PaintOperation { return OP_CODE; } - public static void apply(WireBuffer buffer) { + public static void apply(@NonNull WireBuffer buffer) { buffer.start(Operations.MATRIX_SAVE); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description("Save the matrix and clip to a stack"); } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.matrixSave(); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java index 26c898acb67b..630458499977 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -28,7 +30,7 @@ public class MatrixScale extends DrawBase4 { public static final int OP_CODE = Operations.MATRIX_SCALE; public static final String CLASS_NAME = "MatrixScale"; - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Maker m = MatrixScale::new; read(m, buffer, operations); } @@ -37,16 +39,17 @@ public class MatrixScale extends DrawBase4 { return OP_CODE; } + @NonNull public static String name() { return CLASS_NAME; } @Override - protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) { + protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) { apply(buffer, v1, v2, v3, v4); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description("Draw the specified Oval") .field(DocumentedOperation.FLOAT, "scaleX", "The amount to scale in X") @@ -61,7 +64,7 @@ public class MatrixScale extends DrawBase4 { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.matrixScale(mX1, mY1, mX2, mY2); } @@ -74,7 +77,7 @@ public class MatrixScale extends DrawBase4 { * @param x2 end x of the DrawOval * @param y2 end y of the DrawOval */ - public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) { + public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) { write(buffer, OP_CODE, x1, y1, x2, y2); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java index d64117871eaa..675cf0de4743 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -29,7 +31,7 @@ public class MatrixSkew extends DrawBase2 { public static final int OP_CODE = Operations.MATRIX_SKEW; public static final String CLASS_NAME = "MatrixSkew"; - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Maker m = MatrixSkew::new; read(m, buffer, operations); } @@ -38,16 +40,17 @@ public class MatrixSkew extends DrawBase2 { return OP_CODE; } + @NonNull public static String name() { return CLASS_NAME; } @Override - protected void write(WireBuffer buffer, float v1, float v2) { + protected void write(@NonNull WireBuffer buffer, float v1, float v2) { apply(buffer, v1, v2); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description("Current matrix with the specified skew.") .field(FLOAT, "skewX", "The amount to skew in X") @@ -60,7 +63,7 @@ public class MatrixSkew extends DrawBase2 { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.matrixSkew(mV1, mV2); } @@ -71,7 +74,7 @@ public class MatrixSkew extends DrawBase2 { * @param x1 start x of DrawOval * @param y1 start y of the DrawOval */ - public static void apply(WireBuffer buffer, float x1, float y1) { + public static void apply(@NonNull WireBuffer buffer, float x1, float y1) { write(buffer, OP_CODE, x1, y1); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java index e008292f1107..b0a7d352dfe3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -28,7 +30,7 @@ public class MatrixTranslate extends DrawBase2 { public static final int OP_CODE = Operations.MATRIX_TRANSLATE; public static final String CLASS_NAME = "MatrixTranslate"; - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Maker m = MatrixTranslate::new; read(m, buffer, operations); } @@ -37,16 +39,17 @@ public class MatrixTranslate extends DrawBase2 { return OP_CODE; } + @NonNull public static String name() { return CLASS_NAME; } @Override - protected void write(WireBuffer buffer, float v1, float v2) { + protected void write(@NonNull WireBuffer buffer, float v1, float v2) { apply(buffer, v1, v2); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, "MatrixTranslate") .description("Preconcat the current matrix with the specified translation") .field(DocumentedOperation.FLOAT, "dx", "The distance to translate in X") @@ -59,7 +62,7 @@ public class MatrixTranslate extends DrawBase2 { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.matrixTranslate(mV1, mV2); } @@ -70,7 +73,7 @@ public class MatrixTranslate extends DrawBase2 { * @param x1 start x of DrawOval * @param y1 start y of the DrawOval */ - public static void apply(WireBuffer buffer, float x1, float y1) { + public static void apply(@NonNull WireBuffer buffer, float x1, float y1) { write(buffer, OP_CODE, x1, y1); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java index fa6e2712a0bb..6310521e4010 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.UTF8; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -47,10 +49,11 @@ public class NamedVariable implements Operation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mVarId, mVarType, mVarName); } + @NonNull @Override public String toString() { return "VariableName[" @@ -61,6 +64,7 @@ public class NamedVariable implements Operation { + mVarType; } + @NonNull public static String name() { return CLASS_NAME; } @@ -77,21 +81,22 @@ public class NamedVariable implements Operation { * @param varType The type of variable * @param text String */ - public static void apply(WireBuffer buffer, int varId, int varType, String text) { + public static void apply( + @NonNull WireBuffer buffer, int varId, int varType, @NonNull String text) { buffer.start(Operations.NAMED_VARIABLE); buffer.writeInt(varId); buffer.writeInt(varType); buffer.writeUTF8(text); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int varId = buffer.readInt(); int varType = buffer.readInt(); String text = buffer.readUTF8(MAX_STRING_SIZE); operations.add(new NamedVariable(varId, varType, text)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Data Operations", OP_CODE, CLASS_NAME) .description("Add a string name for an ID") .field(DocumentedOperation.INT, "varId", "id to label") @@ -100,10 +105,11 @@ public class NamedVariable implements Operation { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.loadVariableName(mVarName, mVarId, mVarType); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java index 095a0106b3d7..527d5610788c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -33,31 +35,33 @@ import java.util.List; public class PaintData extends PaintOperation implements VariableSupport { private static final int OP_CODE = Operations.PAINT_VALUES; private static final String CLASS_NAME = "PaintData"; - public PaintBundle mPaintData = new PaintBundle(); + @NonNull public PaintBundle mPaintData = new PaintBundle(); public static final int MAX_STRING_SIZE = 4000; public PaintData() {} @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { mPaintData.updateVariables(context); } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { mPaintData.registerVars(context, this); } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mPaintData); } + @NonNull @Override public String toString() { return "PaintData " + "\"" + mPaintData + "\""; } + @NonNull public static String name() { return CLASS_NAME; } @@ -66,31 +70,32 @@ public class PaintData extends PaintOperation implements VariableSupport { return OP_CODE; } - public static void apply(WireBuffer buffer, PaintBundle paintBundle) { + public static void apply(@NonNull WireBuffer buffer, @NonNull PaintBundle paintBundle) { buffer.start(Operations.PAINT_VALUES); paintBundle.writeBundle(buffer); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { PaintData data = new PaintData(); data.mPaintData.readBundle(buffer); operations.add(data); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Data Operations", OP_CODE, CLASS_NAME) .description("Encode a Paint ") .field(INT, "length", "id string") .field(INT_ARRAY, "paint", "length", "path encoded as floats"); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.applyPaint(mPaintData); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java index 13d5a49a584b..06a1fec36196 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java @@ -18,6 +18,9 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -43,7 +46,7 @@ public class PathData implements Operation, VariableSupport { } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { for (int i = 0; i < mFloatPath.length; i++) { float v = mFloatPath[i]; if (Utils.isVariable(v)) { @@ -55,7 +58,7 @@ public class PathData implements Operation, VariableSupport { } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { for (float v : mFloatPath) { if (Float.isNaN(v)) { context.listensTo(Utils.idFromNan(v), this); @@ -64,15 +67,17 @@ public class PathData implements Operation, VariableSupport { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mInstanceId, mOutputPath); } + @NonNull @Override public String deepToString(String indent) { return pathString(mFloatPath); } + @NonNull @Override public String toString() { return "PathData[" + mInstanceId + "] = " + "\"" + deepToString(" ") + "\""; @@ -102,6 +107,7 @@ public class PathData implements Operation, VariableSupport { public static final float CLOSE_NAN = Utils.asNan(CLOSE); public static final float DONE_NAN = Utils.asNan(DONE); + @NonNull public static String name() { return CLASS_NAME; } @@ -110,7 +116,7 @@ public class PathData implements Operation, VariableSupport { return OP_CODE; } - public static void apply(WireBuffer buffer, int id, float[] data) { + public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) { buffer.start(Operations.DATA_PATH); buffer.writeInt(id); buffer.writeInt(data.length); @@ -119,7 +125,7 @@ public class PathData implements Operation, VariableSupport { } } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int imageId = buffer.readInt(); int len = buffer.readInt(); float[] data = new float[len]; @@ -129,7 +135,7 @@ public class PathData implements Operation, VariableSupport { operations.add(new PathData(imageId, data)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Data Operations", OP_CODE, CLASS_NAME) .description("Encode a Path ") .field(DocumentedOperation.INT, "id", "id string") @@ -137,7 +143,8 @@ public class PathData implements Operation, VariableSupport { .field(FLOAT_ARRAY, "pathData", "length", "path encoded as floats"); } - public static String pathString(float[] path) { + @NonNull + public static String pathString(@Nullable float[] path) { if (path == null) { return "null"; } @@ -186,7 +193,7 @@ public class PathData implements Operation, VariableSupport { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.loadPathData(mInstanceId, mOutputPath); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java index 4a8f5324b74e..6ff9ad73e35f 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteComposeOperation; @@ -172,10 +174,11 @@ public class RootContentBehavior implements RemoteComposeOperation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mScroll, mAlignment, mSizing, mMode); } + @NonNull @Override public String toString() { return "ROOT_CONTENT_BEHAVIOR scroll: " @@ -187,15 +190,17 @@ public class RootContentBehavior implements RemoteComposeOperation { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.setRootContentBehavior(mScroll, mAlignment, mSizing, mMode); } + @NonNull @Override public String deepToString(String indent) { return toString(); } + @NonNull public static String name() { return CLASS_NAME; } @@ -204,7 +209,8 @@ public class RootContentBehavior implements RemoteComposeOperation { return OP_CODE; } - public static void apply(WireBuffer buffer, int scroll, int alignment, int sizing, int mode) { + public static void apply( + @NonNull WireBuffer buffer, int scroll, int alignment, int sizing, int mode) { buffer.start(OP_CODE); buffer.writeInt(scroll); buffer.writeInt(alignment); @@ -212,7 +218,7 @@ public class RootContentBehavior implements RemoteComposeOperation { buffer.writeInt(mode); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int scroll = buffer.readInt(); int alignment = buffer.readInt(); int sizing = buffer.readInt(); @@ -222,7 +228,7 @@ public class RootContentBehavior implements RemoteComposeOperation { operations.add(rootContentBehavior); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Protocol Operations", OP_CODE, CLASS_NAME) .description("Describes the behaviour of the root") .field(DocumentedOperation.INT, "scroll", "scroll") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java index bff902926fd3..c2d62a7b3ac4 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteComposeOperation; @@ -41,25 +43,28 @@ public class RootContentDescription implements RemoteComposeOperation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mContentDescription); } + @NonNull @Override public String toString() { return "RootContentDescription " + mContentDescription; } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.setDocumentContentDescription(mContentDescription); } + @NonNull @Override public String deepToString(String indent) { return toString(); } + @NonNull public static String name() { return CLASS_NAME; } @@ -68,18 +73,18 @@ public class RootContentDescription implements RemoteComposeOperation { return OP_CODE; } - public static void apply(WireBuffer buffer, int contentDescription) { + public static void apply(@NonNull WireBuffer buffer, int contentDescription) { buffer.start(Operations.ROOT_CONTENT_DESCRIPTION); buffer.writeInt(contentDescription); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int contentDescription = buffer.readInt(); RootContentDescription header = new RootContentDescription(contentDescription); operations.add(header); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Protocol Operations", OP_CODE, CLASS_NAME) .description("Content description of root") .field(DocumentedOperation.INT, "id", "id of Int"); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java index 7ec7879bf8b2..ae61c3aa01c9 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java @@ -22,6 +22,9 @@ import static com.android.internal.widget.remotecompose.core.documentation.Docum import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.UTF8; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -43,17 +46,17 @@ public class ShaderData implements Operation, VariableSupport { private static final String CLASS_NAME = "ShaderData"; int mShaderTextId; // the actual text of a shader int mShaderID; // allows shaders to be referenced by number - HashMap<String, float[]> mUniformRawFloatMap = null; - HashMap<String, float[]> mUniformFloatMap = null; - HashMap<String, int[]> mUniformIntMap = null; - HashMap<String, Integer> mUniformBitmapMap = null; + @Nullable HashMap<String, float[]> mUniformRawFloatMap = null; + @Nullable HashMap<String, float[]> mUniformFloatMap = null; + @Nullable HashMap<String, int[]> mUniformIntMap = null; + @Nullable HashMap<String, Integer> mUniformBitmapMap = null; public ShaderData( int shaderID, int shaderTextId, - HashMap<String, float[]> floatMap, - HashMap<String, int[]> intMap, - HashMap<String, Integer> bitmapMap) { + @Nullable HashMap<String, float[]> floatMap, + @Nullable HashMap<String, int[]> intMap, + @Nullable HashMap<String, Integer> bitmapMap) { mShaderID = shaderID; mShaderTextId = shaderTextId; if (floatMap != null) { @@ -89,6 +92,7 @@ public class ShaderData implements Operation, VariableSupport { * * @return Names of all uniform floats or empty array */ + @NonNull public String[] getUniformFloatNames() { if (mUniformFloatMap == null) return new String[0]; return mUniformFloatMap.keySet().toArray(new String[0]); @@ -109,6 +113,7 @@ public class ShaderData implements Operation, VariableSupport { * * @return Name of all integer uniforms */ + @NonNull public String[] getUniformIntegerNames() { if (mUniformIntMap == null) return new String[0]; return mUniformIntMap.keySet().toArray(new String[0]); @@ -129,6 +134,7 @@ public class ShaderData implements Operation, VariableSupport { * * @return Name of all bitmap uniforms */ + @NonNull public String[] getUniformBitmapNames() { if (mUniformBitmapMap == null) return new String[0]; return mUniformBitmapMap.keySet().toArray(new String[0]); @@ -145,7 +151,7 @@ public class ShaderData implements Operation, VariableSupport { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply( buffer, mShaderID, @@ -155,13 +161,14 @@ public class ShaderData implements Operation, VariableSupport { mUniformBitmapMap); } + @NonNull @Override public String toString() { return "SHADER DATA " + mShaderID; } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { for (String name : mUniformRawFloatMap.keySet()) { float[] value = mUniformRawFloatMap.get(name); float[] out = null; @@ -178,7 +185,7 @@ public class ShaderData implements Operation, VariableSupport { } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { for (String name : mUniformRawFloatMap.keySet()) { float[] value = mUniformRawFloatMap.get(name); for (float v : value) { @@ -189,6 +196,7 @@ public class ShaderData implements Operation, VariableSupport { } } + @NonNull public static String name() { return CLASS_NAME; } @@ -208,12 +216,12 @@ public class ShaderData implements Operation, VariableSupport { * @param bitmapMap the map of bitmap uniforms */ public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, int shaderID, int shaderTextId, - HashMap<String, float[]> floatMap, - HashMap<String, int[]> intMap, - HashMap<String, Integer> bitmapMap) { + @Nullable HashMap<String, float[]> floatMap, + @Nullable HashMap<String, int[]> intMap, + @Nullable HashMap<String, Integer> bitmapMap) { buffer.start(OP_CODE); buffer.writeInt(shaderID); @@ -256,7 +264,7 @@ public class ShaderData implements Operation, VariableSupport { } } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int shaderID = buffer.readInt(); int shaderTextId = buffer.readInt(); HashMap<String, float[]> floatMap = null; @@ -308,7 +316,7 @@ public class ShaderData implements Operation, VariableSupport { operations.add(new ShaderData(shaderID, shaderTextId, floatMap, intMap, bitmapMap)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Data Operations", OP_CODE, CLASS_NAME) .description("Shader") .field(DocumentedOperation.INT, "shaderID", "id of shader") @@ -326,10 +334,11 @@ public class ShaderData implements Operation, VariableSupport { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.loadShader(mShaderID, this); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java index 638324950f88..dbaef7ef7ae1 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.UTF8; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -42,15 +44,17 @@ public class TextData implements Operation, SerializableToString { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mTextId, mText); } + @NonNull @Override public String toString() { return "TextData[" + mTextId + "] = \"" + Utils.trimString(mText, 10) + "\""; } + @NonNull public static String name() { return CLASS_NAME; } @@ -59,20 +63,20 @@ public class TextData implements Operation, SerializableToString { return OP_CODE; } - public static void apply(WireBuffer buffer, int textId, String text) { + public static void apply(@NonNull WireBuffer buffer, int textId, @NonNull String text) { buffer.start(OP_CODE); buffer.writeInt(textId); buffer.writeUTF8(text); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int textId = buffer.readInt(); String text = buffer.readUTF8(MAX_STRING_SIZE); operations.add(new TextData(textId, text)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Data Operations", OP_CODE, CLASS_NAME) .description("Encode a string ") .field(DocumentedOperation.INT, "id", "id string") @@ -80,20 +84,22 @@ public class TextData implements Operation, SerializableToString { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.loadText(mTextId, mText); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append(indent, getSerializedName() + "<" + mTextId + "> = \"" + mText + "\""); } + @NonNull private String getSerializedName() { return "DATA_TEXT"; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java index 0d966d10384a..fb5087f3fee3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -87,10 +89,11 @@ public class TextFromFloat implements Operation, VariableSupport { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mTextId, mValue, mDigitsBefore, mDigitsAfter, mFlags); } + @NonNull @Override public String toString() { return "TextFromFloat[" @@ -106,19 +109,20 @@ public class TextFromFloat implements Operation, VariableSupport { } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { if (Float.isNaN(mValue)) { mOutValue = context.getFloat(Utils.idFromNan(mValue)); } } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (Float.isNaN(mValue)) { context.listensTo(Utils.idFromNan(mValue), this); } } + @NonNull public static String name() { return CLASS_NAME; } @@ -138,7 +142,7 @@ public class TextFromFloat implements Operation, VariableSupport { * @param flags flags that control if and how to fill the empty spots */ public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, int textId, float value, short digitsBefore, @@ -151,7 +155,7 @@ public class TextFromFloat implements Operation, VariableSupport { buffer.writeInt(flags); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int textId = buffer.readInt(); float value = buffer.readFloat(); int tmp = buffer.readInt(); @@ -162,7 +166,7 @@ public class TextFromFloat implements Operation, VariableSupport { operations.add(new TextFromFloat(textId, value, pre, post, flags)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Expressions Operations", OP_CODE, CLASS_NAME) .description("Draw text along path object") .field(DocumentedOperation.INT, "textId", "id of the text generated") @@ -173,12 +177,13 @@ public class TextFromFloat implements Operation, VariableSupport { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { float v = mOutValue; String s = StringUtils.floatToString(v, mDigitsBefore, mDigitsAfter, mPre, mAfter); context.loadText(mTextId, s); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java index b04d698fa36c..2129edde2f59 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -48,10 +50,11 @@ public class TextLookup implements Operation, VariableSupport { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mTextId, mDataSetId, mIndex); } + @NonNull @Override public String toString() { return "TextLookup[" @@ -63,19 +66,20 @@ public class TextLookup implements Operation, VariableSupport { } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { if (Float.isNaN(mIndex)) { mOutIndex = context.getFloat(Utils.idFromNan(mIndex)); } } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (Float.isNaN(mIndex)) { context.listensTo(Utils.idFromNan(mIndex), this); } } + @NonNull public static String name() { return CLASS_NAME; } @@ -92,21 +96,21 @@ public class TextLookup implements Operation, VariableSupport { * @param dataSet float pointer to the array/list to turn int a string * @param index index of element to return */ - public static void apply(WireBuffer buffer, int textId, int dataSet, float index) { + public static void apply(@NonNull WireBuffer buffer, int textId, int dataSet, float index) { buffer.start(OP_CODE); buffer.writeInt(textId); buffer.writeInt(dataSet); buffer.writeFloat(index); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int textId = buffer.readInt(); int dataSetId = buffer.readInt(); float index = buffer.readFloat(); operations.add(new TextLookup(textId, dataSetId, index)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Expressions Operations", OP_CODE, CLASS_NAME) .description("Look an array and turn into a text object") .field(INT, "textId", "id of the text generated") @@ -115,11 +119,12 @@ public class TextLookup implements Operation, VariableSupport { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { int id = context.getCollectionsAccess().getId(mDataSetId, (int) mOutIndex); context.loadText(mTextId, context.getText(id)); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java index 171bea249273..ea550cbe010c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -45,10 +47,11 @@ public class TextLookupInt implements Operation, VariableSupport { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mTextId, mDataSetId, mIndex); } + @NonNull @Override public String toString() { return "TextLookupInt[" @@ -60,15 +63,16 @@ public class TextLookupInt implements Operation, VariableSupport { } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { mOutIndex = context.getInteger(mIndex); } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { context.listensTo(mIndex, this); } + @NonNull public static String name() { return CLASS_NAME; } @@ -85,21 +89,21 @@ public class TextLookupInt implements Operation, VariableSupport { * @param dataSet float pointer to the array/list to turn int a string * @param indexId index of element to return */ - public static void apply(WireBuffer buffer, int textId, int dataSet, int indexId) { + public static void apply(@NonNull WireBuffer buffer, int textId, int dataSet, int indexId) { buffer.start(OP_CODE); buffer.writeInt(textId); buffer.writeInt(dataSet); buffer.writeInt(indexId); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int textId = buffer.readInt(); int dataSetId = buffer.readInt(); int indexId = buffer.readInt(); operations.add(new TextLookupInt(textId, dataSetId, indexId)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Expressions Operations", OP_CODE, CLASS_NAME) .description("Look up an array and turn into a text object") .field(DocumentedOperation.INT, "textId", "id of the text generated") @@ -108,11 +112,12 @@ public class TextLookupInt implements Operation, VariableSupport { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { int id = context.getCollectionsAccess().getId(mDataSetId, (int) mOutIndex); context.loadText(mTextId, context.getText(id)); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java index 78cc674a22e9..fa18b4ddb2a2 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -41,15 +43,17 @@ public class TextMerge implements Operation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mTextId, mSrcId1, mSrcId2); } + @NonNull @Override public String toString() { return "TextMerge[" + mTextId + "] = [" + mSrcId1 + " ] + [ " + mSrcId2 + "]"; } + @NonNull public static String name() { return CLASS_NAME; } @@ -66,14 +70,14 @@ public class TextMerge implements Operation { * @param srcId1 source text 1 * @param srcId2 source text 2 */ - public static void apply(WireBuffer buffer, int textId, int srcId1, int srcId2) { + public static void apply(@NonNull WireBuffer buffer, int textId, int srcId1, int srcId2) { buffer.start(OP_CODE); buffer.writeInt(textId); buffer.writeInt(srcId1); buffer.writeInt(srcId2); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int textId = buffer.readInt(); int srcId1 = buffer.readInt(); int srcId2 = buffer.readInt(); @@ -81,7 +85,7 @@ public class TextMerge implements Operation { operations.add(new TextMerge(textId, srcId1, srcId2)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Data Operations", OP_CODE, CLASS_NAME) .description("Merge two string into one") .field(DocumentedOperation.INT, "textId", "id of the text") @@ -90,12 +94,13 @@ public class TextMerge implements Operation { } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { String str1 = context.getText(mSrcId1); String str2 = context.getText(mSrcId2); context.loadText(mTextId, str1 + str2); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); 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 845f25d0cd67..1e90ab128e93 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 @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteComposeOperation; @@ -49,25 +51,28 @@ public class Theme implements RemoteComposeOperation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mTheme); } + @NonNull @Override public String toString() { return "SET_THEME " + mTheme; } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.setTheme(mTheme); } + @NonNull @Override public String deepToString(String indent) { return indent + toString(); } + @NonNull public static String name() { return CLASS_NAME; } @@ -76,17 +81,17 @@ public class Theme implements RemoteComposeOperation { return OP_CODE; } - public static void apply(WireBuffer buffer, int theme) { + public static void apply(@NonNull WireBuffer buffer, int theme) { buffer.start(OP_CODE); buffer.writeInt(theme); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int theme = buffer.readInt(); operations.add(new Theme(theme)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Protocol Operations", OP_CODE, CLASS_NAME) .description("Set a theme") .field(INT, "THEME", "theme id") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java new file mode 100644 index 000000000000..b25a7f6ea09d --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java @@ -0,0 +1,599 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations; + +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY; +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.TouchListener; +import com.android.internal.widget.remotecompose.core.VariableSupport; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.operations.layout.Component; +import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression; +import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap; +import com.android.internal.widget.remotecompose.core.operations.utilities.touch.VelocityEasing; + +import java.util.Arrays; +import java.util.List; + +/** + * Operation to deal with Touch handling (typically on canvas) This support handling of many typical + * touch behaviours. Including animating to Notched, positions. and tweaking the dynamics of the + * animation. + */ +public class TouchExpression implements Operation, VariableSupport, TouchListener { + private static final int OP_CODE = Operations.TOUCH_EXPRESSION; + private static final String CLASS_NAME = "TouchExpression"; + private float mDefValue; + private float mOutDefValue; + public int mId; + public float[] mSrcExp; + int mMode = 1; // 0 = delta, 1 = absolute + float mMax = 1; + float mMin = 1; + float mOutMax = 1; + float mOutMin = 1; + float mValue = 0; + boolean mUnmodified = true; + public float[] mPreCalcValue; + private float mLastChange = Float.NaN; + private float mLastCalculatedValue = Float.NaN; + AnimatedFloatExpression mExp = new AnimatedFloatExpression(); + public static final int MAX_EXPRESSION_SIZE = 32; + private VelocityEasing mEasyTouch = new VelocityEasing(); + private boolean mEasingToStop = false; + private float mTouchUpTime = 0; + private float mCurrentValue = Float.NaN; + private boolean mTouchDown = false; + float mMaxTime = 1; + float mMaxAcceleration = 5; + float mMaxVelocity = 7; + int mStopMode = 0; + boolean mWrapMode = false; + float[] mNotches; + float[] mStopSpec; + int mTouchEffects; + float mVelocityId; + + public static final int STOP_GENTLY = 0; + public static final int STOP_ENDS = 2; + public static final int STOP_INSTANTLY = 1; + public static final int STOP_NOTCHES_EVEN = 3; + public static final int STOP_NOTCHES_PERCENTS = 4; + public static final int STOP_NOTCHES_ABSOLUTE = 5; + public static final int STOP_ABSOLUTE_POS = 6; + + public TouchExpression( + int id, + float[] exp, + float defValue, + float min, + float max, + int touchEffects, + float velocityId, + int stopMode, + float[] stopSpec, + float[] easingSpec) { + this.mId = id; + this.mSrcExp = exp; + mOutDefValue = mDefValue = defValue; + mMode = STOP_ABSOLUTE_POS == stopMode ? 1 : 0; + mOutMax = mMax = max; + mTouchEffects = touchEffects; + mVelocityId = velocityId; + if (Float.isNaN(min) && Utils.idFromNan(min) == 0) { + mWrapMode = true; + } else { + mOutMin = mMin = min; + } + mStopMode = stopMode; + mStopSpec = stopSpec; + if (easingSpec != null) { + Utils.log("easingSpec " + Arrays.toString(easingSpec)); + if (easingSpec.length >= 4) { + if (Float.floatToRawIntBits(easingSpec[0]) == 0) { + Utils.log("easingSpec[2] " + easingSpec[2]); + mMaxTime = easingSpec[1]; + mMaxAcceleration = easingSpec[2]; + mMaxVelocity = easingSpec[3]; + } + } + } + } + + @Override + public void updateVariables(RemoteContext context) { + + if (mPreCalcValue == null || mPreCalcValue.length != mSrcExp.length) { + mPreCalcValue = new float[mSrcExp.length]; + } + if (Float.isNaN(mMax)) { + mOutMax = context.getFloat(Utils.idFromNan(mMax)); + } + if (Float.isNaN(mMin)) { + mOutMin = context.getFloat(Utils.idFromNan(mMin)); + } + if (Float.isNaN(mDefValue)) { + mOutDefValue = context.getFloat(Utils.idFromNan(mDefValue)); + } + + boolean value_changed = false; + for (int i = 0; i < mSrcExp.length; i++) { + float v = mSrcExp[i]; + if (Float.isNaN(v) + && !AnimatedFloatExpression.isMathOperator(v) + && !NanMap.isDataVariable(v)) { + float newValue = context.getFloat(Utils.idFromNan(v)); + + mPreCalcValue[i] = newValue; + + } else { + mPreCalcValue[i] = mSrcExp[i]; + } + } + float v = mLastCalculatedValue; + if (value_changed) { // inputs changed check if output changed + v = mExp.eval(mPreCalcValue, mPreCalcValue.length); + if (v != mLastCalculatedValue) { + mLastChange = context.getAnimationTime(); + mLastCalculatedValue = v; + } else { + value_changed = false; + } + } + } + + @Override + public void registerListening(RemoteContext context) { + if (Float.isNaN(mMax)) { + context.listensTo(Utils.idFromNan(mMax), this); + } + if (Float.isNaN(mMin)) { + context.listensTo(Utils.idFromNan(mMin), this); + } + if (Float.isNaN(mDefValue)) { + context.listensTo(Utils.idFromNan(mDefValue), this); + } + context.addTouchListener(this); + for (float v : mSrcExp) { + if (Float.isNaN(v) + && !AnimatedFloatExpression.isMathOperator(v) + && !NanMap.isDataVariable(v)) { + context.listensTo(Utils.idFromNan(v), this); + } + } + } + + private float wrap(float pos) { + if (!mWrapMode) { + return pos; + } + pos = pos % mOutMax; + if (pos < 0) { + pos += mOutMax; + } + return pos; + } + + private float getStopPosition(float pos, float slope) { + float target = pos + slope / mMaxAcceleration; + if (mWrapMode) { + pos = wrap(pos); + target = pos += +slope / mMaxAcceleration; + } else { + target = Math.max(Math.min(target, mOutMax), mOutMin); + } + float[] positions = new float[mStopSpec.length]; + float min = (mWrapMode) ? 0 : mOutMin; + + switch (mStopMode) { + case STOP_ENDS: + return ((pos + slope) > (mOutMax + min) / 2) ? mOutMax : min; + case STOP_INSTANTLY: + return pos; + case STOP_NOTCHES_EVEN: + int evenSpacing = (int) mStopSpec[0]; + float step = (mOutMax - min) / evenSpacing; + + float notch = min + step * (int) (0.5f + (target - mOutMin) / step); + + notch = Math.max(Math.min(notch, mOutMax), min); + return notch; + case STOP_NOTCHES_PERCENTS: + positions = new float[mStopSpec.length]; + float minPos = min; + float minPosDist = Math.abs(mOutMin - target); + for (int i = 0; i < positions.length; i++) { + float p = mOutMin + mStopSpec[i] * (mOutMax - mOutMin); + float dist = Math.abs(p - target); + if (minPosDist > dist) { + minPosDist = dist; + minPos = p; + } + } + return minPos; + case STOP_NOTCHES_ABSOLUTE: + positions = mStopSpec; + minPos = mOutMin; + minPosDist = Math.abs(mOutMin - target); + for (int i = 0; i < positions.length; i++) { + float dist = Math.abs(positions[i] - target); + if (minPosDist > dist) { + minPosDist = dist; + minPos = positions[i]; + } + } + return minPos; + case STOP_GENTLY: + default: + return target; + } + } + + void haptic(RemoteContext context) { + int touch = ((mTouchEffects) & 0xFF); + if ((mTouchEffects & (1 << 15)) != 0) { + touch = context.getInteger(mTouchEffects & 0x7FFF); + } + + context.hapticEffect(touch); + } + + float mLastValue = 0; + + void crossNotchCheck(RemoteContext context) { + float prev = mLastValue; + float next = mCurrentValue; + mLastValue = next; + + // System.out.println(mStopMode + " " + prev + " -> " + next); + float min = (mWrapMode) ? 0 : mOutMin; + float max = mOutMax; + + switch (mStopMode) { + case STOP_ENDS: + if (((min - prev) * (max - prev) < 0) ^ ((min - next) * (max - next)) < 0) { + haptic(context); + } + break; + case STOP_INSTANTLY: + haptic(context); + break; + case STOP_NOTCHES_EVEN: + int evenSpacing = (int) mStopSpec[0]; + float step = (max - min) / evenSpacing; + if ((int) ((prev - min) / step) != (int) ((next - min) / step)) { + haptic(context); + } + break; + case STOP_NOTCHES_PERCENTS: + for (int i = 0; i < mStopSpec.length; i++) { + float p = mOutMin + mStopSpec[i] * (mOutMax - mOutMin); + if ((prev - p) * (next - p) < 0) { + haptic(context); + } + } + break; + case STOP_NOTCHES_ABSOLUTE: + for (int i = 0; i < mStopSpec.length; i++) { + float p = mStopSpec[i]; + if ((prev - p) * (next - p) < 0) { + haptic(context); + } + } + break; + case STOP_GENTLY: + } + } + + float mScrLeft, mScrRight, mScrTop, mScrBottom; + + @Override + public void apply(RemoteContext context) { + Component comp = context.lastComponent; + if (comp != null) { + float x = comp.getX(); + float y = comp.getY(); + float w = comp.getWidth(); + float h = comp.getHeight(); + comp = comp.getParent(); + while (comp != null) { + x += comp.getX(); + y += comp.getY(); + comp = comp.getParent(); + } + mScrLeft = x; + mScrTop = y; + mScrRight = w + x; + mScrBottom = h + y; + } + updateVariables(context); + if (mUnmodified) { + mCurrentValue = mOutDefValue; + + context.loadFloat(mId, wrap(mCurrentValue)); + return; + } + if (mEasingToStop) { + float time = context.getAnimationTime() - mTouchUpTime; + float value = mEasyTouch.getPos(time); + mCurrentValue = value; + value = wrap(value); + context.loadFloat(mId, value); + if (mEasyTouch.getDuration() < time) { + mEasingToStop = false; + } + crossNotchCheck(context); + return; + } + if (mTouchDown) { + float value = + mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length); + if (mMode == 0) { + value = mValueAtDown + (value - mDownTouchValue); + } + if (mWrapMode) { + value = wrap(value); + } else { + value = Math.min(Math.max(value, mOutMin), mOutMax); + } + mCurrentValue = value; + } + crossNotchCheck(context); + context.loadFloat(mId, wrap(mCurrentValue)); + } + + float mValueAtDown; // The currently "displayed" value at down + float mDownTouchValue; // The calculated value at down + + @Override + public void touchDown(RemoteContext context, float x, float y) { + + if (!(x >= mScrLeft && x <= mScrRight && y >= mScrTop && y <= mScrBottom)) { + Utils.log("NOT IN WINDOW " + x + ", " + y + " " + mScrLeft + ", " + mScrTop); + return; + } + mTouchDown = true; + mUnmodified = false; + if (mMode == 0) { + mValueAtDown = context.getFloat(mId); + mDownTouchValue = + mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length); + } + } + + @Override + public void touchUp(RemoteContext context, float x, float y, float dx, float dy) { + // calculate the slope (using small changes) + if (!mTouchDown) { + return; + } + mTouchDown = false; + float dt = 0.0001f; + if (mStopMode == STOP_INSTANTLY) { + return; + } + float v = mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length); + for (int i = 0; i < mSrcExp.length; i++) { + if (Float.isNaN(mSrcExp[i])) { + int id = Utils.idFromNan(mSrcExp[i]); + if (id == RemoteContext.ID_TOUCH_POS_X) { + mPreCalcValue[i] = x + dx * dt; + } else if (id == RemoteContext.ID_TOUCH_POS_Y) { + mPreCalcValue[i] = y + dy * dt; + } + } + } + float vdt = mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length); + float slope = (vdt - v) / dt; // the rate of change with respect to the dx,dy movement + float value = context.getFloat(mId); + + mTouchUpTime = context.getAnimationTime(); + + float dest = getStopPosition(value, slope); + mEasyTouch.config(value, dest, slope, mMaxTime, mMaxAcceleration, mMaxVelocity, null); + mEasingToStop = true; + } + + @Override + public void touchDrag(RemoteContext context, float x, float y) { + if (!mTouchDown) { + return; + } + apply(context); + context.getDocument().getRootLayoutComponent().needsRepaint(); + } + + @Override + public void write(WireBuffer buffer) { + apply( + buffer, + mId, + mValue, + mMin, + mMax, + mVelocityId, + mTouchEffects, + mSrcExp, + mStopMode, + mNotches, + null); + } + + @Override + public String toString() { + String[] labels = new String[mSrcExp.length]; + for (int i = 0; i < mSrcExp.length; i++) { + if (Float.isNaN(mSrcExp[i])) { + labels[i] = "[" + Utils.idStringFromNan(mSrcExp[i]) + "]"; + } + } + if (mPreCalcValue == null) { + return CLASS_NAME + + "[" + + mId + + "] = (" + + AnimatedFloatExpression.toString(mSrcExp, labels) + + ")"; + } + return CLASS_NAME + + "[" + + mId + + "] = (" + + AnimatedFloatExpression.toString(mPreCalcValue, labels) + + ")"; + } + + // ===================== static ====================== + + public static String name() { + return CLASS_NAME; + } + + public static int id() { + return OP_CODE; + } + + /** + * Writes out the operation to the buffer + * + * @param buffer The buffer to write to + * @param id the id of the resulting float + * @param value the float expression array + */ + public static void apply( + WireBuffer buffer, + int id, + float value, + float min, + float max, + float velocityId, + int touchEffects, + float[] exp, + int touchMode, + float[] touchSpec, + float[] easingSpec) { + buffer.start(OP_CODE); + buffer.writeInt(id); + buffer.writeFloat(value); + buffer.writeFloat(min); + buffer.writeFloat(max); + buffer.writeFloat(velocityId); + buffer.writeInt(touchEffects); + buffer.writeInt(exp.length); + for (float v : exp) { + buffer.writeFloat(v); + } + int len = 0; + if (touchSpec != null) { + len = touchSpec.length; + } + buffer.writeInt((touchMode << 16) | len); + for (int i = 0; i < len; i++) { + buffer.writeFloat(touchSpec[i]); + } + + if (easingSpec != null) { + len = easingSpec.length; + } else { + len = 0; + } + buffer.writeInt(len); + for (int i = 0; i < len; i++) { + buffer.writeFloat(easingSpec[i]); + } + } + + public static void read(WireBuffer buffer, List<Operation> operations) { + int id = buffer.readInt(); + float startValue = buffer.readFloat(); + float min = buffer.readFloat(); + float max = buffer.readFloat(); + float velocityId = buffer.readFloat(); // TODO future support + int touchEffects = buffer.readInt(); + int len = buffer.readInt(); + int valueLen = len & 0xFFFF; + if (valueLen > MAX_EXPRESSION_SIZE) { + throw new RuntimeException("Float expression to long"); + } + float[] exp = new float[valueLen]; + for (int i = 0; i < exp.length; i++) { + exp[i] = buffer.readFloat(); + } + int stopLogic = buffer.readInt(); + int stopLen = stopLogic & 0xFFFF; + int stopMode = stopLogic >> 16; + + Utils.log("stopMode " + stopMode + " stopLen " + stopLen); + float[] stopsData = new float[stopLen]; + for (int i = 0; i < stopsData.length; i++) { + stopsData[i] = buffer.readFloat(); + } + int easingLen = buffer.readInt(); + + float[] easingData = new float[easingLen]; + for (int i = 0; i < easingData.length; i++) { + easingData[i] = buffer.readFloat(); + } + + operations.add( + new TouchExpression( + id, + exp, + startValue, + min, + max, + touchEffects, + velocityId, + stopMode, + stopsData, + easingData)); + } + + public static void documentation(DocumentationBuilder doc) { + doc.operation("Expressions Operations", OP_CODE, CLASS_NAME) + .description("A Float expression") + .field(INT, "id", "The id of the Color") + .field(SHORT, "expression_length", "expression length") + .field(SHORT, "animation_length", "animation description length") + .field( + FLOAT_ARRAY, + "expression", + "expression_length", + "Sequence of Floats representing and expression") + .field( + FLOAT_ARRAY, + "AnimationSpec", + "animation_length", + "Sequence of Floats representing animation curve") + .field(FLOAT, "duration", "> time in sec") + .field(INT, "bits", "> WRAP|INITALVALUE | TYPE ") + .field(FLOAT_ARRAY, "spec", "> [SPEC PARAMETERS] ") + .field(FLOAT, "initialValue", "> [Initial value] ") + .field(FLOAT, "wrapValue", "> [Wrap value] "); + } + + @Override + public String deepToString(String indent) { + return indent + toString(); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java index 8ebb40cab806..03f7e0563eeb 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations; +import android.annotation.NonNull; + /** Utilities to be used across all core operations */ public class Utils { public static float asNan(int v) { @@ -30,11 +32,13 @@ public class Utils { return v - 0x100000000L; } + @NonNull public static String idStringFromNan(float value) { int b = Float.floatToRawIntBits(value) & 0x3FFFFF; return idString(b); } + @NonNull public static String idString(int b) { return (b > 0xFFFFF) ? "A_" + (b & 0xFFFFF) : "" + b; } @@ -50,7 +54,8 @@ public class Utils { * @param n * @return */ - public static String trimString(String str, int n) { + @NonNull + public static String trimString(@NonNull String str, int n) { if (str.length() > n) { str = str.substring(0, n - 3) + "..."; } @@ -145,6 +150,7 @@ public class Utils { * @param color * @return */ + @NonNull public static String colorInt(int color) { String str = "000000000000" + Integer.toHexString(color); return "0x" + str.substring(str.length() - 8); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java new file mode 100644 index 000000000000..e789710bb113 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java @@ -0,0 +1,80 @@ +/* + * 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.internal.widget.remotecompose.core.operations.layout; + +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.operations.Utils; +import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation; +import com.android.internal.widget.remotecompose.core.operations.utilities.easing.GeneralEasing; + +public class AnimatableValue { + boolean mIsVariable = false; + int mId = 0; + float mValue = 0f; + + boolean mAnimate = false; + long mAnimateTargetTime = 0; + float mAnimateDuration = 300f; + float mTargetRotationX; + float mStartRotationX; + + int mMotionEasingType = GeneralEasing.CUBIC_STANDARD; + FloatAnimation mMotionEasing; + + public AnimatableValue(float value) { + if (Utils.isVariable(value)) { + mId = Utils.idFromNan(value); + mIsVariable = true; + } else { + mValue = value; + } + } + + public float getValue() { + return mValue; + } + + public float evaluate(PaintContext context) { + if (!mIsVariable) { + return mValue; + } + float value = context.getContext().mRemoteComposeState.getFloat(mId); + + if (value != mValue && !mAnimate) { + // animate + mStartRotationX = mValue; + mTargetRotationX = value; + mAnimate = true; + mAnimateTargetTime = System.currentTimeMillis(); + mMotionEasing = + new FloatAnimation( + mMotionEasingType, mAnimateDuration / 1000f, null, 0f, Float.NaN); + mMotionEasing.setTargetValue(1f); + } + if (mAnimate) { + float elapsed = System.currentTimeMillis() - mAnimateTargetTime; + float p = mMotionEasing.get(elapsed / mAnimateDuration); + mValue = (1 - p) * mStartRotationX + p * mTargetRotationX; + if (p >= 1f) { + mAnimate = false; + } + } else { + mValue = mTargetRotationX; + } + + return mValue; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java index 9d80d3cc40b0..988651895bae 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations.layout; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.WireBuffer; @@ -38,6 +40,7 @@ public class CanvasContent extends Component implements ComponentStartOperation super(parent, componentId, animationId, x, y, width, height); } + @NonNull public static String name() { return "CanvasContent"; } @@ -46,29 +49,30 @@ public class CanvasContent extends Component implements ComponentStartOperation return Operations.LAYOUT_CANVAS_CONTENT; } + @NonNull @Override protected String getSerializedName() { return "CANVAS_CONTENT"; } - public static void apply(WireBuffer buffer, int componentId) { + public static void apply(@NonNull WireBuffer buffer, int componentId) { buffer.start(Operations.LAYOUT_CANVAS_CONTENT); buffer.writeInt(componentId); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int componentId = buffer.readInt(); operations.add(new CanvasContent(componentId, 0, 0, 0, 0, null, -1)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", id(), name()) .field(INT, "COMPONENT_ID", "unique id for this component") .description("Container for canvas commands."); } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mComponentId); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickHandler.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickHandler.java new file mode 100644 index 000000000000..0ca72fae9eea --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickHandler.java @@ -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. + */ +package com.android.internal.widget.remotecompose.core.operations.layout; + +import com.android.internal.widget.remotecompose.core.CoreDocument; +import com.android.internal.widget.remotecompose.core.RemoteContext; + +/** Interface to represent operations that can handle click events */ +public interface ClickHandler { + + /** + * callback for a click event + * + * @param context the current context + * @param document the current document + * @param component the component on which the click has been received + * @param x the x position of the click in document coordinates + * @param y the y position of the click in document coordinates + */ + void onClick( + RemoteContext context, CoreDocument document, Component component, float x, float y); +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java index d5ff07df54cd..b567538b1c4e 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; @@ -37,7 +40,7 @@ import java.util.List; /** Represents a click modifier + actions */ public class ClickModifierOperation extends PaintOperation - implements ModifierOperation, DecoratorComponent { + implements ModifierOperation, DecoratorComponent, ClickHandler { private static final int OP_CODE = Operations.MODIFIER_CLICK; long mAnimateRippleStart = 0; @@ -48,9 +51,9 @@ public class ClickModifierOperation extends PaintOperation float mWidth = 0; float mHeight = 0; - public float[] locationInWindow = new float[2]; + @NonNull public float[] locationInWindow = new float[2]; - PaintBundle mPaint = new PaintBundle(); + @NonNull PaintBundle mPaint = new PaintBundle(); public void animateRipple(float x, float y) { mAnimateRippleStart = System.currentTimeMillis(); @@ -58,17 +61,19 @@ public class ClickModifierOperation extends PaintOperation mAnimateRippleY = y; } - public ArrayList<Operation> mList = new ArrayList<>(); + @NonNull public ArrayList<Operation> mList = new ArrayList<>(); + @NonNull public ArrayList<Operation> getList() { return mList; } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer); } + @NonNull @Override public String toString() { return "ClickModifier"; @@ -83,13 +88,14 @@ public class ClickModifierOperation extends PaintOperation } } + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { if (mAnimateRippleStart == 0) { return; } @@ -137,7 +143,7 @@ public class ClickModifierOperation extends PaintOperation } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append(indent, "CLICK_MODIFIER"); for (Operation o : mList) { if (o instanceof ActionOperation) { @@ -148,7 +154,11 @@ public class ClickModifierOperation extends PaintOperation @Override public void onClick( - RemoteContext context, CoreDocument document, Component component, float x, float y) { + RemoteContext context, + CoreDocument document, + @NonNull Component component, + float x, + float y) { if (!component.isVisible()) { return; } @@ -163,19 +173,20 @@ public class ClickModifierOperation extends PaintOperation } } + @NonNull public static String name() { return "ClickModifier"; } - public static void apply(WireBuffer buffer) { + public static void apply(@NonNull WireBuffer buffer) { buffer.start(OP_CODE); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(WireBuffer buffer, @NonNull List<Operation> operations) { operations.add(new ClickModifierOperation()); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", OP_CODE, name()) .description( "Click modifier. This operation contains" diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java index 96dffca2042f..f4f4ee2a3416 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -31,7 +34,6 @@ import com.android.internal.widget.remotecompose.core.operations.layout.animatio import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Measurable; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; -import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers; import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; @@ -52,16 +54,23 @@ public class Component extends PaintOperation implements Measurable, Serializabl protected int mAnimationId = -1; public Visibility mVisibility = Visibility.VISIBLE; public Visibility mScheduledVisibility = Visibility.VISIBLE; - public ArrayList<Operation> mList = new ArrayList<>(); + @NonNull public ArrayList<Operation> mList = new ArrayList<>(); public PaintOperation mPreTranslate; public boolean mNeedsMeasure = true; public boolean mNeedsRepaint = false; - public AnimateMeasure mAnimateMeasure; - public AnimationSpec mAnimationSpec = new AnimationSpec(); + @Nullable public AnimateMeasure mAnimateMeasure; + @NonNull public AnimationSpec mAnimationSpec = new AnimationSpec(); public boolean mFirstLayout = true; - PaintBundle mPaint = new PaintBundle(); - protected HashSet<ComponentValue> mComponentValues = new HashSet<>(); + @NonNull PaintBundle mPaint = new PaintBundle(); + @NonNull protected HashSet<ComponentValue> mComponentValues = new HashSet<>(); + + protected float mZIndex = 0f; + public float getZIndex() { + return mZIndex; + } + + @NonNull public ArrayList<Operation> getList() { return mList; } @@ -115,7 +124,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl * * @param context the current context */ - private void updateComponentValues(RemoteContext context) { + private void updateComponentValues(@NonNull RemoteContext context) { if (DEBUG) { System.out.println( "UPDATE COMPONENT VALUES (" @@ -172,7 +181,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl this(parent, componentId, -1, x, y, width, height); } - public Component(Component component) { + public Component(@NonNull Component component) { this( component.mParent, component.mComponentId, @@ -212,7 +221,10 @@ public class Component extends PaintOperation implements Measurable, Serializabl * * @param context the current context */ - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { + Component prev = context.lastComponent; + context.lastComponent = this; + if (!mComponentValues.isEmpty()) { updateComponentValues(context); } @@ -224,6 +236,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl o.apply(context); } } + context.lastComponent = prev; } public void addComponentValue(ComponentValue v) { @@ -283,14 +296,14 @@ public class Component extends PaintOperation implements Measurable, Serializabl float maxWidth, float minHeight, float maxHeight, - MeasurePass measure) { + @NonNull MeasurePass measure) { ComponentMeasure m = measure.get(this); m.setW(mWidth); m.setH(mHeight); } @Override - public void layout(RemoteContext context, MeasurePass measure) { + public void layout(@NonNull RemoteContext context, @NonNull MeasurePass measure) { ComponentMeasure m = measure.get(this); if (!mFirstLayout && context.isAnimationEnabled() @@ -332,7 +345,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl mFirstLayout = false; } - public float[] locationInWindow = new float[2]; + @NonNull public float[] locationInWindow = new float[2]; public boolean contains(float x, float y) { locationInWindow[0] = 0f; @@ -353,13 +366,57 @@ public class Component extends PaintOperation implements Measurable, Serializabl if (op instanceof Component) { ((Component) op).onClick(context, document, x, y); } - if (op instanceof ComponentModifiers) { - ((ComponentModifiers) op).onClick(context, document, this, x, y); + if (op instanceof ClickHandler) { + ((ClickHandler) op).onClick(context, document, this, x, y); + } + } + } + + public void onTouchDown(RemoteContext context, CoreDocument document, float x, float y) { + if (!contains(x, y)) { + return; + } + for (Operation op : mList) { + if (op instanceof Component) { + ((Component) op).onTouchDown(context, document, x, y); + } + if (op instanceof TouchHandler) { + ((TouchHandler) op).onTouchDown(context, document, this, x, y); + } + } + } + + public void onTouchUp( + RemoteContext context, CoreDocument document, float x, float y, boolean force) { + if (!force && !contains(x, y)) { + return; + } + for (Operation op : mList) { + if (op instanceof Component) { + ((Component) op).onTouchUp(context, document, x, y, force); + } + if (op instanceof TouchHandler) { + ((TouchHandler) op).onTouchUp(context, document, this, x, y); + } + } + } + + public void onTouchCancel( + RemoteContext context, CoreDocument document, float x, float y, boolean force) { + if (!force && !contains(x, y)) { + return; + } + for (Operation op : mList) { + if (op instanceof Component) { + ((Component) op).onTouchCancel(context, document, x, y, force); + } + if (op instanceof TouchHandler) { + ((TouchHandler) op).onTouchCancel(context, document, this, x, y); } } } - public void getLocationInWindow(float[] value) { + public void getLocationInWindow(@NonNull float[] value) { value[0] += mX; value[1] += mY; if (mParent != null) { @@ -372,6 +429,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl } } + @NonNull @Override public String toString() { return "COMPONENT(<" @@ -393,14 +451,14 @@ public class Component extends PaintOperation implements Measurable, Serializabl + ") "; } + @NonNull protected String getSerializedName() { return "COMPONENT"; } @Override - public void serializeToString(int indent, StringSerializer serializer) { - serializer.append( - indent, + public void serializeToString(int indent, @NonNull StringSerializer serializer) { + String content = getSerializedName() + " [" + mComponentId @@ -416,9 +474,9 @@ public class Component extends PaintOperation implements Measurable, Serializabl + ", " + mHeight + "] " - + mVisibility - // + " [" + mNeedsMeasure + ", " + mNeedsRepaint + "]" - ); + + mVisibility; + // + " [" + mNeedsMeasure + ", " + mNeedsRepaint + "]" + serializer.append(indent, content); } @Override @@ -427,6 +485,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl } /** Returns the top-level RootLayoutComponent */ + @NonNull public RootLayoutComponent getRoot() throws Exception { if (this instanceof RootLayoutComponent) { return (RootLayoutComponent) this; @@ -441,6 +500,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl return (RootLayoutComponent) p; } + @NonNull @Override public String deepToString(String indent) { StringBuilder builder = new StringBuilder(); @@ -477,6 +537,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl } } + @NonNull public String content() { StringBuilder builder = new StringBuilder(); for (Operation op : mList) { @@ -487,6 +548,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl return builder.toString(); } + @NonNull public String textContent() { StringBuilder builder = new StringBuilder(); for (Operation ignored : mList) { @@ -499,7 +561,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl return builder.toString(); } - public void debugBox(Component component, PaintContext context) { + public void debugBox(@NonNull Component component, @NonNull PaintContext context) { float width = component.mWidth; float height = component.mHeight; @@ -536,13 +598,15 @@ public class Component extends PaintOperation implements Measurable, Serializabl return 0f; } - public void paintingComponent(PaintContext context) { + public void paintingComponent(@NonNull PaintContext context) { if (mPreTranslate != null) { mPreTranslate.paint(context); } + Component prev = context.getContext().lastComponent; + context.getContext().lastComponent = this; context.save(); context.translate(mX, mY); - if (context.isDebug()) { + if (context.isVisualDebug()) { debugBox(this, context); } for (Operation op : mList) { @@ -554,9 +618,10 @@ public class Component extends PaintOperation implements Measurable, Serializabl } } context.restore(); + context.getContext().lastComponent = prev; } - public boolean applyAnimationAsNeeded(PaintContext context) { + public boolean applyAnimationAsNeeded(@NonNull PaintContext context) { if (context.isAnimationEnabled() && mAnimateMeasure != null) { mAnimateMeasure.apply(context); needsRepaint(); @@ -566,8 +631,8 @@ public class Component extends PaintOperation implements Measurable, Serializabl } @Override - public void paint(PaintContext context) { - if (context.isDebug()) { + public void paint(@NonNull PaintContext context) { + if (context.isVisualDebug()) { context.save(); context.translate(mX, mY); context.savePaint(); @@ -594,7 +659,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl paintingComponent(context); } - public void getComponents(ArrayList<Component> components) { + public void getComponents(@NonNull ArrayList<Component> components) { for (Operation op : mList) { if (op instanceof Component) { components.add((Component) op); @@ -602,7 +667,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl } } - public void getData(ArrayList<TextData> data) { + public void getData(@NonNull ArrayList<TextData> data) { for (Operation op : mList) { if (op instanceof TextData) { data.add((TextData) op); @@ -631,6 +696,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl return mNeedsRepaint; } + @Nullable public Component getComponent(int cid) { if (mComponentId == cid || mAnimationId == cid) { return this; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java index c83ee487a8ea..f370e20677e8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -26,10 +29,11 @@ import java.util.List; public class ComponentEnd implements Operation { @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer); } + @NonNull @Override public String toString() { return "COMPONENT_END"; @@ -40,11 +44,13 @@ public class ComponentEnd implements Operation { // nothing } + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } + @NonNull public static String name() { return "ComponentEnd"; } @@ -53,7 +59,7 @@ public class ComponentEnd implements Operation { return Operations.COMPONENT_END; } - public static void apply(WireBuffer buffer) { + public static void apply(@NonNull WireBuffer buffer) { buffer.start(Operations.COMPONENT_END); } @@ -61,11 +67,11 @@ public class ComponentEnd implements Operation { return 1 + 4 + 4 + 4; } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(WireBuffer buffer, @NonNull List<Operation> operations) { operations.add(new ComponentEnd()); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", id(), name()) .description( "End tag for components / layouts. This operation marks the end" diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java index 72cc9b6d2613..f250d9ac4a01 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java @@ -18,6 +18,9 @@ package com.android.internal.widget.remotecompose.core.operations.layout; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -69,10 +72,11 @@ public class ComponentStart implements ComponentStartOperation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mType, mComponentId, mWidth, mHeight); } + @NonNull @Override public String toString() { return "COMPONENT_START (type " @@ -90,8 +94,9 @@ public class ComponentStart implements ComponentStartOperation { + ")"; } + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } @@ -119,6 +124,7 @@ public class ComponentStart implements ComponentStartOperation { public static final int LAYOUT_ROW = 15; public static final int LAYOUT_COLUMN = 16; + @NonNull public static String typeDescription(int type) { switch (type) { case DEFAULT: @@ -152,6 +158,7 @@ public class ComponentStart implements ComponentStartOperation { } } + @NonNull public static String name() { return "ComponentStart"; } @@ -161,7 +168,7 @@ public class ComponentStart implements ComponentStartOperation { } public static void apply( - WireBuffer buffer, int type, int componentId, float width, float height) { + @NonNull WireBuffer buffer, int type, int componentId, float width, float height) { buffer.start(Operations.COMPONENT_START); buffer.writeInt(type); buffer.writeInt(componentId); @@ -173,7 +180,7 @@ public class ComponentStart implements ComponentStartOperation { return 1 + 4 + 4 + 4; } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int type = buffer.readInt(); int componentId = buffer.readInt(); float width = buffer.readFloat(); @@ -181,7 +188,7 @@ public class ComponentStart implements ComponentStartOperation { operations.add(new ComponentStart(type, componentId, width, height)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", id(), name()) .description( "Basic component encapsulating draw commands." + "This is not resizable.") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java index 314650fcd597..bb4311996df0 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java @@ -15,7 +15,6 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout; -import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.RemoteContext; /** @@ -24,7 +23,4 @@ import com.android.internal.widget.remotecompose.core.RemoteContext; */ public interface DecoratorComponent { void layout(RemoteContext context, float width, float height); - - void onClick( - RemoteContext context, CoreDocument document, Component component, float x, float y); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java index 8172502d092f..e0923dfb48fb 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.operations.BitmapData; @@ -25,18 +28,22 @@ import com.android.internal.widget.remotecompose.core.operations.TextData; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation; import java.util.ArrayList; /** Component with modifiers and children */ public class LayoutComponent extends Component { - protected WidthModifierOperation mWidthModifier = null; - protected HeightModifierOperation mHeightModifier = null; + @Nullable protected WidthModifierOperation mWidthModifier = null; + @Nullable protected HeightModifierOperation mHeightModifier = null; + @Nullable protected ZIndexModifierOperation mZIndexModifier = null; + @Nullable protected GraphicsLayerModifierOperation mGraphicsLayerModifier = null; // Margins protected float mMarginLeft = 0f; @@ -49,8 +56,10 @@ public class LayoutComponent extends Component { protected float mPaddingTop = 0f; protected float mPaddingBottom = 0f; - protected ComponentModifiers mComponentModifiers = new ComponentModifiers(); - protected ArrayList<Component> mChildrenComponents = new ArrayList<>(); + @NonNull protected ComponentModifiers mComponentModifiers = new ComponentModifiers(); + @NonNull protected ArrayList<Component> mChildrenComponents = new ArrayList<>(); + + protected boolean mChildrenHaveZIndex = false; public LayoutComponent( Component parent, @@ -95,15 +104,25 @@ public class LayoutComponent extends Component { return mPaddingBottom; } + @Nullable public WidthModifierOperation getWidthModifier() { return mWidthModifier; } + @Nullable public HeightModifierOperation getHeightModifier() { return mHeightModifier; } - protected LayoutComponentContent mContent = null; + @Override + public float getZIndex() { + if (mZIndexModifier != null) { + return mZIndexModifier.getValue(); + } + return mZIndex; + } + + @Nullable protected LayoutComponentContent mContent = null; // Should be removed after ImageLayout is in private static final boolean USE_IMAGE_TEMP_FIX = true; @@ -164,6 +183,9 @@ public class LayoutComponent extends Component { for (Component c : mChildrenComponents) { c.mParent = this; mList.add(c); + if (c instanceof LayoutComponent && ((LayoutComponent) c).mZIndexModifier != null) { + mChildrenHaveZIndex = true; + } } mX = 0f; @@ -209,6 +231,12 @@ public class LayoutComponent extends Component { mHeightModifier = (HeightModifierOperation) op; applyVerticalMargin = false; } + if (op instanceof ZIndexModifierOperation) { + mZIndexModifier = (ZIndexModifierOperation) op; + } + if (op instanceof GraphicsLayerModifierOperation) { + mGraphicsLayerModifier = (GraphicsLayerModifierOperation) op; + } } if (mWidthModifier == null) { mWidthModifier = new WidthModifierOperation(DimensionModifierOperation.Type.WRAP); @@ -220,24 +248,64 @@ public class LayoutComponent extends Component { setHeight(computeModifierDefinedHeight()); } + @NonNull @Override public String toString() { return "UNKNOWN LAYOUT_COMPONENT"; } @Override - public void paintingComponent(PaintContext context) { + public void paintingComponent(@NonNull PaintContext context) { + Component prev = context.getContext().lastComponent; + context.getContext().lastComponent = this; context.save(); context.translate(mX, mY); + if (mGraphicsLayerModifier != null) { + context.startGraphicsLayer((int) getWidth(), (int) getHeight()); + float scaleX = mGraphicsLayerModifier.getScaleX(); + float scaleY = mGraphicsLayerModifier.getScaleY(); + float rotationX = mGraphicsLayerModifier.getRotationX(); + float rotationY = mGraphicsLayerModifier.getRotationY(); + float rotationZ = mGraphicsLayerModifier.getRotationZ(); + float shadowElevation = mGraphicsLayerModifier.getShadowElevation(); + float transformOriginX = mGraphicsLayerModifier.getTransformOriginX(); + float transformOriginY = mGraphicsLayerModifier.getTransformOriginY(); + float alpha = mGraphicsLayerModifier.getAlpha(); + int renderEffectId = mGraphicsLayerModifier.getRenderEffectId(); + context.setGraphicsLayer( + scaleX, + scaleY, + rotationX, + rotationY, + rotationZ, + shadowElevation, + transformOriginX, + transformOriginY, + alpha, + renderEffectId); + } mComponentModifiers.paint(context); float tx = mPaddingLeft; float ty = mPaddingTop; context.translate(tx, ty); - for (Component child : mChildrenComponents) { - child.paint(context); + if (mChildrenHaveZIndex) { + // TODO -- should only sort when something has changed + ArrayList<Component> sorted = new ArrayList<Component>(mChildrenComponents); + sorted.sort((a, b) -> (int) (a.getZIndex() - b.getZIndex())); + for (Component child : sorted) { + child.paint(context); + } + } else { + for (Component child : mChildrenComponents) { + child.paint(context); + } + } + if (mGraphicsLayerModifier != null) { + context.endGraphicsLayer(); } context.translate(-tx, -ty); context.restore(); + context.getContext().lastComponent = prev; } /** Traverse the modifiers to compute indicated dimension */ @@ -248,7 +316,8 @@ public class LayoutComponent extends Component { for (Operation c : mComponentModifiers.getList()) { if (c instanceof WidthModifierOperation) { WidthModifierOperation o = (WidthModifierOperation) c; - if (o.getType() == DimensionModifierOperation.Type.EXACT) { + if (o.getType() == DimensionModifierOperation.Type.EXACT + || o.getType() == DimensionModifierOperation.Type.EXACT_DP) { w = o.getValue(); } break; @@ -291,7 +360,8 @@ public class LayoutComponent extends Component { for (Operation c : mComponentModifiers.getList()) { if (c instanceof HeightModifierOperation) { HeightModifierOperation o = (HeightModifierOperation) c; - if (o.getType() == DimensionModifierOperation.Type.EXACT) { + if (o.getType() == DimensionModifierOperation.Type.EXACT + || o.getType() == DimensionModifierOperation.Type.EXACT_DP) { h = o.getValue(); } break; @@ -326,6 +396,7 @@ public class LayoutComponent extends Component { return t + b; } + @NonNull public ArrayList<Component> getChildrenComponents() { return mChildrenComponents; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java index 66fd053c4b5e..0a085b43401c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations.layout; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.WireBuffer; @@ -38,6 +40,7 @@ public class LayoutComponentContent extends Component implements ComponentStartO super(parent, componentId, animationId, x, y, width, height); } + @NonNull public static String name() { return "LayoutContent"; } @@ -46,22 +49,23 @@ public class LayoutComponentContent extends Component implements ComponentStartO return Operations.LAYOUT_CONTENT; } + @NonNull @Override protected String getSerializedName() { return "CONTENT"; } - public static void apply(WireBuffer buffer, int componentId) { + public static void apply(@NonNull WireBuffer buffer, int componentId) { buffer.start(Operations.LAYOUT_CONTENT); buffer.writeInt(componentId); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int componentId = buffer.readInt(); operations.add(new LayoutComponentContent(componentId, 0, 0, 0, 0, null, -1)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", id(), name()) .field(INT, "COMPONENT_ID", "unique id for this component") .description( @@ -71,7 +75,7 @@ public class LayoutComponentContent extends Component implements ComponentStartO } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mComponentId); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java new file mode 100644 index 000000000000..c4df075bb9e8 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java @@ -0,0 +1,109 @@ +/* + * 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.internal.widget.remotecompose.core.operations.layout; + +import com.android.internal.widget.remotecompose.core.CoreDocument; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.operations.TextData; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; + +import java.util.ArrayList; + +public abstract class ListActionsOperation extends PaintOperation + implements ModifierOperation, DecoratorComponent { + + String mOperationName; + float mWidth = 0; + float mHeight = 0; + + private final float[] mLocationInWindow = new float[2]; + + public ListActionsOperation(String operationName) { + mOperationName = operationName; + } + + public ArrayList<Operation> mList = new ArrayList<>(); + + public ArrayList<Operation> getList() { + return mList; + } + + @Override + public String toString() { + return mOperationName; + } + + @Override + public void apply(RemoteContext context) { + for (Operation op : mList) { + if (op instanceof TextData) { + op.apply(context); + } + } + } + + @Override + public String deepToString(String indent) { + return (indent != null ? indent : "") + toString(); + } + + @Override + public void paint(PaintContext context) {} + + @Override + public void layout(RemoteContext context, float width, float height) { + mWidth = width; + mHeight = height; + } + + @Override + public void serializeToString(int indent, StringSerializer serializer) { + serializer.append(indent, mOperationName); + for (Operation o : mList) { + if (o instanceof ActionOperation) { + ((ActionOperation) o).serializeToString(indent + 1, serializer); + } + } + } + + public boolean applyActions( + RemoteContext context, + CoreDocument document, + Component component, + float x, + float y, + boolean force) { + if (!force && !component.isVisible()) { + return false; + } + if (!force && !component.contains(x, y)) { + return false; + } + mLocationInWindow[0] = 0f; + mLocationInWindow[1] = 0f; + component.getLocationInWindow(mLocationInWindow); + for (Operation o : mList) { + if (o instanceof ActionOperation) { + ((ActionOperation) o).runAction(context, document, component, x, y); + } + } + return true; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java index 3086d6aaa777..c90077b88782 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -26,10 +29,11 @@ import java.util.List; public class LoopEnd implements Operation { @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer); } + @NonNull @Override public String toString() { return "LOOP_END"; @@ -40,11 +44,13 @@ public class LoopEnd implements Operation { // nothing } + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } + @NonNull public static String name() { return "LoopEnd"; } @@ -53,15 +59,15 @@ public class LoopEnd implements Operation { return Operations.LOOP_END; } - public static void apply(WireBuffer buffer) { + public static void apply(@NonNull WireBuffer buffer) { buffer.start(id()); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(WireBuffer buffer, @NonNull List<Operation> operations) { operations.add(new LoopEnd()); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Operations", id(), name()).description("End tag for loops"); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java index 691000810c20..eeaeafd284c0 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -30,7 +33,7 @@ import java.util.List; public class LoopOperation extends PaintOperation { private static final int OP_CODE = Operations.LOOP_START; - public ArrayList<Operation> mList = new ArrayList<>(); + @NonNull public ArrayList<Operation> mList = new ArrayList<>(); int mIndexVariableId; float mUntil = 12; @@ -49,27 +52,30 @@ public class LoopOperation extends PaintOperation { mIndexVariableId = indexId; } + @NonNull public ArrayList<Operation> getList() { return mList; } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mUntil, mFrom, mStep, mIndexVariableId); } + @NonNull @Override public String toString() { return "LoopOperation"; } + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { if (mIndexVariableId == 0) { for (float i = mFrom; i < mUntil; i += mStep) { for (Operation op : mList) { @@ -89,11 +95,13 @@ public class LoopOperation extends PaintOperation { } } + @NonNull public static String name() { return "Loop"; } - public static void apply(WireBuffer buffer, float count, float from, float step, int indexId) { + public static void apply( + @NonNull WireBuffer buffer, float count, float from, float step, int indexId) { buffer.start(OP_CODE); buffer.writeFloat(count); buffer.writeFloat(from); @@ -101,7 +109,7 @@ public class LoopOperation extends PaintOperation { buffer.writeInt(indexId); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float count = buffer.readFloat(); float from = buffer.readFloat(); float step = buffer.readFloat(); @@ -109,7 +117,7 @@ public class LoopOperation extends PaintOperation { operations.add(new LoopOperation(count, from, step, indexId)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Operations", OP_CODE, name()) .description("Loop. This operation execute" + " a list of action in a loop"); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java index fe726ac78791..bd8d1f0ba9dd 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierEnd.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -23,16 +26,17 @@ import com.android.internal.widget.remotecompose.core.documentation.Documentatio import java.util.List; -public class ClickModifierEnd implements Operation { +public class OperationsListEnd implements Operation { @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer); } + @NonNull @Override public String toString() { - return "CLICK_END"; + return "LIST_END"; } @Override @@ -40,31 +44,31 @@ public class ClickModifierEnd implements Operation { // nothing } + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } + @NonNull public static String name() { - return "ClickModifierEnd"; + return "ListEnd"; } public static int id() { - return Operations.MODIFIER_CLICK_END; + return Operations.OPERATIONS_LIST_END; } - public static void apply(WireBuffer buffer) { + public static void apply(@NonNull WireBuffer buffer) { buffer.start(id()); } - public static void read(WireBuffer buffer, List<Operation> operations) { - operations.add(new ClickModifierEnd()); + public static void read(WireBuffer buffer, @NonNull List<Operation> operations) { + operations.add(new OperationsListEnd()); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", id(), name()) - .description( - "End tag for click modifiers. This operation marks the end" - + "of a click modifier"); + .description("End tag for list of operations."); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java index 680bb0b064d1..524ae59e70ec 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations.layout; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -34,7 +36,8 @@ import java.util.List; /** Represents the root layout component. Entry point to the component tree layout/paint. */ public class RootLayoutComponent extends Component implements ComponentStartOperation { - int mCurrentId = -1; + private int mCurrentId = -1; + private boolean mHasTouchListeners = false; public RootLayoutComponent( int componentId, @@ -52,6 +55,7 @@ public class RootLayoutComponent extends Component implements ComponentStartOper super(parent, componentId, -1, x, y, width, height); } + @NonNull @Override public String toString() { return "ROOT " @@ -69,7 +73,7 @@ public class RootLayoutComponent extends Component implements ComponentStartOper } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append( indent, "ROOT [" @@ -89,6 +93,15 @@ public class RootLayoutComponent extends Component implements ComponentStartOper } /** + * Set the flag to traverse the tree when touch events happen + * + * @param value true to indicate that the tree has touch listeners + */ + public void setHasTouchListeners(boolean value) { + mHasTouchListeners = value; + } + + /** * Traverse the hierarchy and assign generated ids to component without ids. Most components * would already have ids assigned during the document creation, but this allow us to take care * of any components added during the inflation. @@ -100,7 +113,7 @@ public class RootLayoutComponent extends Component implements ComponentStartOper assignId(this); } - private void assignId(Component component) { + private void assignId(@NonNull Component component) { if (component.mComponentId == -1) { mCurrentId--; component.mComponentId = mCurrentId; @@ -113,7 +126,7 @@ public class RootLayoutComponent extends Component implements ComponentStartOper } /** This will measure then layout the tree of components */ - public void layout(RemoteContext context) { + public void layout(@NonNull RemoteContext context) { if (!mNeedsMeasure) { return; } @@ -134,7 +147,7 @@ public class RootLayoutComponent extends Component implements ComponentStartOper } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { mNeedsRepaint = false; context.getContext().lastComponent = this; context.save(); @@ -152,13 +165,15 @@ public class RootLayoutComponent extends Component implements ComponentStartOper context.restore(); } + @NonNull public String displayHierarchy() { StringSerializer serializer = new StringSerializer(); displayHierarchy(this, 0, serializer); return serializer.toString(); } - public void displayHierarchy(Component component, int indent, StringSerializer serializer) { + public void displayHierarchy( + @NonNull Component component, int indent, @NonNull StringSerializer serializer) { component.serializeToString(indent, serializer); for (Operation c : component.mList) { if (c instanceof ComponentModifiers) { @@ -171,6 +186,7 @@ public class RootLayoutComponent extends Component implements ComponentStartOper } } + @NonNull public static String name() { return "RootLayout"; } @@ -179,17 +195,17 @@ public class RootLayoutComponent extends Component implements ComponentStartOper return Operations.LAYOUT_ROOT; } - public static void apply(WireBuffer buffer, int componentId) { + public static void apply(@NonNull WireBuffer buffer, int componentId) { buffer.start(Operations.LAYOUT_ROOT); buffer.writeInt(componentId); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int componentId = buffer.readInt(); operations.add(new RootLayoutComponent(componentId, 0, 0, 0, 0, null, -1)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", id(), name()) .field(INT, "COMPONENT_ID", "unique id for this component") .description( @@ -199,7 +215,11 @@ public class RootLayoutComponent extends Component implements ComponentStartOper } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mComponentId); } + + public boolean hasTouchListeners() { + return mHasTouchListeners; + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java new file mode 100644 index 000000000000..486efbd6e00f --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java @@ -0,0 +1,91 @@ +/* + * 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.internal.widget.remotecompose.core.operations.layout; + +import com.android.internal.widget.remotecompose.core.CoreDocument; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; + +import java.util.List; + +/** Represents a touch cancel modifier + actions */ +public class TouchCancelModifierOperation extends ListActionsOperation implements TouchHandler { + + private static final int OP_CODE = Operations.MODIFIER_TOUCH_CANCEL; + + public TouchCancelModifierOperation() { + super("TOUCH_CANCEL_MODIFIER"); + } + + @Override + public void write(WireBuffer buffer) { + apply(buffer); + } + + @Override + public String toString() { + return "TouchCancelModifier"; + } + + @Override + public void apply(RemoteContext context) { + RootLayoutComponent root = context.getDocument().getRootLayoutComponent(); + if (root != null) { + root.setHasTouchListeners(true); + } + super.apply(context); + } + + @Override + public void onTouchDown( + RemoteContext context, CoreDocument document, Component component, float x, float y) { + // nothing + } + + @Override + public void onTouchUp( + RemoteContext context, CoreDocument document, Component component, float x, float y) { + // nothing + } + + @Override + public void onTouchCancel( + RemoteContext context, CoreDocument document, Component component, float x, float y) { + applyActions(context, document, component, x, y, true); + } + + public static String name() { + return "TouchCancelModifier"; + } + + public static void apply(WireBuffer buffer) { + buffer.start(OP_CODE); + } + + public static void read(WireBuffer buffer, List<Operation> operations) { + operations.add(new TouchCancelModifierOperation()); + } + + public static void documentation(DocumentationBuilder doc) { + doc.operation("Modifier Operations", OP_CODE, name()) + .description( + "Touch cancel modifier. This operation contains" + + " a list of action executed on Touch cancel"); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java new file mode 100644 index 000000000000..5d379fe01d61 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java @@ -0,0 +1,93 @@ +/* + * 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.internal.widget.remotecompose.core.operations.layout; + +import com.android.internal.widget.remotecompose.core.CoreDocument; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; + +import java.util.List; + +/** Represents a touch down modifier + actions */ +public class TouchDownModifierOperation extends ListActionsOperation implements TouchHandler { + + private static final int OP_CODE = Operations.MODIFIER_TOUCH_DOWN; + + public TouchDownModifierOperation() { + super("TOUCH_DOWN_MODIFIER"); + } + + @Override + public void write(WireBuffer buffer) { + apply(buffer); + } + + @Override + public String toString() { + return "TouchDownModifier"; + } + + @Override + public void apply(RemoteContext context) { + RootLayoutComponent root = context.getDocument().getRootLayoutComponent(); + if (root != null) { + root.setHasTouchListeners(true); + } + super.apply(context); + } + + @Override + public void onTouchDown( + RemoteContext context, CoreDocument document, Component component, float x, float y) { + if (applyActions(context, document, component, x, y, false)) { + document.appliedTouchOperation(component); + } + } + + @Override + public void onTouchUp( + RemoteContext context, CoreDocument document, Component component, float x, float y) { + // nothing + } + + @Override + public void onTouchCancel( + RemoteContext context, CoreDocument document, Component component, float x, float y) { + // nothing + } + + public static String name() { + return "TouchModifier"; + } + + public static void apply(WireBuffer buffer) { + buffer.start(OP_CODE); + } + + public static void read(WireBuffer buffer, List<Operation> operations) { + operations.add(new TouchDownModifierOperation()); + } + + public static void documentation(DocumentationBuilder doc) { + doc.operation("Modifier Operations", OP_CODE, name()) + .description( + "Touch down modifier. This operation contains" + + " a list of action executed on Touch down"); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java new file mode 100644 index 000000000000..5adfc33b5ef5 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java @@ -0,0 +1,59 @@ +/* + * 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.internal.widget.remotecompose.core.operations.layout; + +import com.android.internal.widget.remotecompose.core.CoreDocument; +import com.android.internal.widget.remotecompose.core.RemoteContext; + +/** Interface to represent operations that can handle touch events */ +public interface TouchHandler { + + /** + * callback for a touch down event + * + * @param context the current context + * @param document the current document + * @param component the component on which the touch has been received + * @param x the x position of the click in document coordinates + * @param y the y position of the click in document coordinates + */ + void onTouchDown( + RemoteContext context, CoreDocument document, Component component, float x, float y); + + /** + * callback for a touch up event + * + * @param context the current context + * @param document the current document + * @param component the component on which the touch has been received + * @param x the x position of the click in document coordinates + * @param y the y position of the click in document coordinates + */ + void onTouchUp( + RemoteContext context, CoreDocument document, Component component, float x, float y); + + /** + * callback for a touch cancel event + * + * @param context the current context + * @param document the current document + * @param component the component on which the touch has been received + * @param x the x position of the click in document coordinates + * @param y the y position of the click in document coordinates + */ + void onTouchCancel( + RemoteContext context, CoreDocument document, Component component, float x, float y); +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java new file mode 100644 index 000000000000..263cc43d5e74 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java @@ -0,0 +1,91 @@ +/* + * 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.internal.widget.remotecompose.core.operations.layout; + +import com.android.internal.widget.remotecompose.core.CoreDocument; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; + +import java.util.List; + +/** Represents a touch up modifier + actions */ +public class TouchUpModifierOperation extends ListActionsOperation implements TouchHandler { + + private static final int OP_CODE = Operations.MODIFIER_TOUCH_UP; + + public TouchUpModifierOperation() { + super("TOUCH_UP_MODIFIER"); + } + + @Override + public void write(WireBuffer buffer) { + apply(buffer); + } + + @Override + public String toString() { + return "TouchUpModifier"; + } + + @Override + public void apply(RemoteContext context) { + RootLayoutComponent root = context.getDocument().getRootLayoutComponent(); + if (root != null) { + root.setHasTouchListeners(true); + } + super.apply(context); + } + + @Override + public void onTouchDown( + RemoteContext context, CoreDocument document, Component component, float x, float y) { + // nothing + } + + @Override + public void onTouchUp( + RemoteContext context, CoreDocument document, Component component, float x, float y) { + applyActions(context, document, component, x, y, true); + } + + @Override + public void onTouchCancel( + RemoteContext context, CoreDocument document, Component component, float x, float y) { + // nothing + } + + public static String name() { + return "TouchUpModifier"; + } + + public static void apply(WireBuffer buffer) { + buffer.start(OP_CODE); + } + + public static void read(WireBuffer buffer, List<Operation> operations) { + operations.add(new TouchUpModifierOperation()); + } + + public static void documentation(DocumentationBuilder doc) { + doc.operation("Modifier Operations", OP_CODE, name()) + .description( + "Touch up modifier. This operation contains" + + " a list of action executed on Touch up"); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java index e45058528859..6036b74efad3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout.animation; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.operations.layout.Component; @@ -44,18 +46,23 @@ public class AnimateMeasure { float mP = 0f; float mVp = 0f; + + @NonNull FloatAnimation mMotionEasing = new FloatAnimation(mMotionEasingType, mDuration / 1000f, null, 0f, Float.NaN); + + @NonNull FloatAnimation mVisibilityEasing = new FloatAnimation( mVisibilityEasingType, mDurationVisibilityChange / 1000f, null, 0f, Float.NaN); + ParticleAnimation mParticleAnimation; public AnimateMeasure( long startTime, - Component component, + @NonNull Component component, ComponentMeasure original, - ComponentMeasure target, + @NonNull ComponentMeasure target, int duration, int durationVisibilityChange, AnimationSpec.ANIMATION enterAnimation, @@ -94,9 +101,9 @@ public class AnimateMeasure { mVp = mVisibilityEasing.get(visibilityProgress); } - public PaintBundle paint = new PaintBundle(); + @NonNull public PaintBundle paint = new PaintBundle(); - public void apply(PaintContext context) { + public void apply(@NonNull PaintContext context) { update(context.getContext().currentTime); mComponent.setX(getX()); @@ -338,7 +345,7 @@ public class AnimateMeasure { } } - public void updateTarget(ComponentMeasure measure, long currentTime) { + public void updateTarget(@NonNull ComponentMeasure measure, long currentTime) { mOriginal.setX(getX()); mOriginal.setY(getY()); mOriginal.setW(getWidth()); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java index 35533cb95190..47abadeb70fa 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java @@ -17,6 +17,9 @@ package com.android.internal.widget.remotecompose.core.operations.layout.animati import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -92,6 +95,7 @@ public class AnimationSpec implements Operation { return mExitAnimation; } + @NonNull @Override public String toString() { return "ANIMATION_SPEC (" + mMotionDuration + " ms)"; @@ -109,7 +113,7 @@ public class AnimationSpec implements Operation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply( buffer, mAnimationId, @@ -126,11 +130,13 @@ public class AnimationSpec implements Operation { // nothing here } + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } + @NonNull public static String name() { return "AnimationSpec"; } @@ -139,10 +145,11 @@ public class AnimationSpec implements Operation { return Operations.ANIMATION_SPEC; } - public static int animationToInt(ANIMATION animation) { + public static int animationToInt(@NonNull ANIMATION animation) { return animation.ordinal(); } + @NonNull public static ANIMATION intToAnimation(int value) { switch (value) { case 0: @@ -167,14 +174,14 @@ public class AnimationSpec implements Operation { } public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, int animationId, int motionDuration, int motionEasingType, int visibilityDuration, int visibilityEasingType, - ANIMATION enterAnimation, - ANIMATION exitAnimation) { + @NonNull ANIMATION enterAnimation, + @NonNull ANIMATION exitAnimation) { buffer.start(Operations.ANIMATION_SPEC); buffer.writeInt(animationId); buffer.writeInt(motionDuration); @@ -185,7 +192,7 @@ public class AnimationSpec implements Operation { buffer.writeInt(animationToInt(exitAnimation)); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int animationId = buffer.readInt(); int motionDuration = buffer.readInt(); int motionEasingType = buffer.readInt(); @@ -205,7 +212,7 @@ public class AnimationSpec implements Operation { operations.add(op); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", id(), name()) .description("define the animation") .field(INT, "animationId", "") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java index 686643fbe1bc..37d20781f4fa 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout.animation; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; @@ -24,14 +26,14 @@ import java.util.ArrayList; import java.util.HashMap; public class ParticleAnimation { - HashMap<Integer, ArrayList<Particle>> mAllParticles = new HashMap<>(); + @NonNull HashMap<Integer, ArrayList<Particle>> mAllParticles = new HashMap<>(); - PaintBundle mPaint = new PaintBundle(); + @NonNull PaintBundle mPaint = new PaintBundle(); public void animate( - PaintContext context, - Component component, - ComponentMeasure start, + @NonNull PaintContext context, + @NonNull Component component, + @NonNull ComponentMeasure start, ComponentMeasure end, float progress) { ArrayList<Particle> particles = mAllParticles.get(component.getComponentId()); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java index 047a968785c4..f3e550903a65 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations.layout.manager import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -75,6 +77,7 @@ public class BoxLayout extends LayoutManager implements ComponentStartOperation verticalPositioning); } + @NonNull @Override public String toString() { return "BOX [" @@ -93,6 +96,7 @@ public class BoxLayout extends LayoutManager implements ComponentStartOperation + mVisibility; } + @NonNull @Override protected String getSerializedName() { return "BOX"; @@ -100,7 +104,11 @@ public class BoxLayout extends LayoutManager implements ComponentStartOperation @Override public void computeWrapSize( - PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) { + PaintContext context, + float maxWidth, + float maxHeight, + @NonNull MeasurePass measure, + @NonNull Size size) { for (Component c : mChildrenComponents) { c.measure(context, 0f, maxWidth, 0f, maxHeight, measure); ComponentMeasure m = measure.get(c); @@ -119,14 +127,14 @@ public class BoxLayout extends LayoutManager implements ComponentStartOperation float maxWidth, float minHeight, float maxHeight, - MeasurePass measure) { + @NonNull MeasurePass measure) { for (Component child : mChildrenComponents) { child.measure(context, minWidth, maxWidth, minHeight, maxHeight, measure); } } @Override - public void internalLayoutMeasure(PaintContext context, MeasurePass measure) { + public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) { ComponentMeasure selfMeasure = measure.get(this); float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight; float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom; @@ -161,6 +169,7 @@ public class BoxLayout extends LayoutManager implements ComponentStartOperation } } + @NonNull public static String name() { return "BoxLayout"; } @@ -170,7 +179,7 @@ public class BoxLayout extends LayoutManager implements ComponentStartOperation } public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, int componentId, int animationId, int horizontalPositioning, @@ -182,7 +191,7 @@ public class BoxLayout extends LayoutManager implements ComponentStartOperation buffer.writeInt(verticalPositioning); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int componentId = buffer.readInt(); int animationId = buffer.readInt(); int horizontalPositioning = buffer.readInt(); @@ -196,7 +205,7 @@ public class BoxLayout extends LayoutManager implements ComponentStartOperation verticalPositioning)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", id(), name()) .description( "Box layout implementation.\n\n" @@ -224,7 +233,7 @@ public class BoxLayout extends LayoutManager implements ComponentStartOperation } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mComponentId, mAnimationId, mHorizontalPositioning, mVerticalPositioning); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java index f79976715ab3..12ff969530b3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations.layout.manager import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -44,6 +46,7 @@ public class CanvasLayout extends BoxLayout { this(parent, componentId, animationId, 0, 0, 0, 0); } + @NonNull @Override public String toString() { return "CANVAS [" @@ -62,11 +65,13 @@ public class CanvasLayout extends BoxLayout { + mVisibility; } + @NonNull @Override protected String getSerializedName() { return "CANVAS"; } + @NonNull public static String name() { return "CanvasLayout"; } @@ -75,19 +80,19 @@ public class CanvasLayout extends BoxLayout { return Operations.LAYOUT_CANVAS; } - public static void apply(WireBuffer buffer, int componentId, int animationId) { + public static void apply(@NonNull WireBuffer buffer, int componentId, int animationId) { buffer.start(Operations.LAYOUT_CANVAS); buffer.writeInt(componentId); buffer.writeInt(animationId); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int componentId = buffer.readInt(); int animationId = buffer.readInt(); operations.add(new CanvasLayout(null, componentId, animationId)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", id(), name()) .description("Canvas implementation. Encapsulate draw operations.\n\n") .field(INT, "COMPONENT_ID", "unique id for this component") @@ -98,7 +103,7 @@ public class CanvasLayout extends BoxLayout { } @Override - public void internalLayoutMeasure(PaintContext context, MeasurePass measure) { + public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) { ComponentMeasure selfMeasure = measure.get(this); float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight; float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom; @@ -112,7 +117,7 @@ public class CanvasLayout extends BoxLayout { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mComponentId, mAnimationId); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java index 402b784343ad..52bf4c54faf6 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations.layout.manager import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -87,6 +89,7 @@ public class ColumnLayout extends LayoutManager implements ComponentStartOperati spacedBy); } + @NonNull @Override public String toString() { return "COLUMN [" @@ -105,14 +108,24 @@ public class ColumnLayout extends LayoutManager implements ComponentStartOperati + mVisibility; } + @NonNull @Override protected String getSerializedName() { return "COLUMN"; } @Override + public boolean isInVerticalFill() { + return super.isInVerticalFill() || childrenHaveVerticalWeights(); + } + + @Override public void computeWrapSize( - PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) { + PaintContext context, + float maxWidth, + float maxHeight, + @NonNull MeasurePass measure, + @NonNull Size size) { DebugLog.s(() -> "COMPUTE WRAP SIZE in " + this + " (" + mComponentId + ")"); int visibleChildrens = 0; for (Component c : mChildrenComponents) { @@ -137,7 +150,7 @@ public class ColumnLayout extends LayoutManager implements ComponentStartOperati float maxWidth, float minHeight, float maxHeight, - MeasurePass measure) { + @NonNull MeasurePass measure) { DebugLog.s(() -> "COMPUTE SIZE in " + this + " (" + mComponentId + ")"); float mh = maxHeight; for (Component child : mChildrenComponents) { @@ -151,7 +164,7 @@ public class ColumnLayout extends LayoutManager implements ComponentStartOperati } @Override - public void internalLayoutMeasure(PaintContext context, MeasurePass measure) { + public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) { ComponentMeasure selfMeasure = measure.get(this); DebugLog.s( () -> @@ -302,6 +315,7 @@ public class ColumnLayout extends LayoutManager implements ComponentStartOperati DebugLog.e(); } + @NonNull public static String name() { return "ColumnLayout"; } @@ -311,7 +325,7 @@ public class ColumnLayout extends LayoutManager implements ComponentStartOperati } public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, int componentId, int animationId, int horizontalPositioning, @@ -325,7 +339,7 @@ public class ColumnLayout extends LayoutManager implements ComponentStartOperati buffer.writeFloat(spacedBy); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int componentId = buffer.readInt(); int animationId = buffer.readInt(); int horizontalPositioning = buffer.readInt(); @@ -341,7 +355,7 @@ public class ColumnLayout extends LayoutManager implements ComponentStartOperati spacedBy)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", id(), name()) .description( "Column layout implementation, positioning components one" @@ -374,7 +388,7 @@ public class ColumnLayout extends LayoutManager implements ComponentStartOperati } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply( buffer, mComponentId, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java index 308ed64ee8ea..0c4d24a4561a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout.managers; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.operations.layout.Component; @@ -27,7 +29,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.measure. /** Base class for layout managers -- resizable components. */ public abstract class LayoutManager extends LayoutComponent implements Measurable { - Size mCachedWrapSize = new Size(0f, 0f); + @NonNull Size mCachedWrapSize = new Size(0f, 0f); public LayoutManager( Component parent, @@ -62,6 +64,38 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl // nothing here } + protected boolean childrenHaveHorizontalWeights() { + for (Component c : mChildrenComponents) { + if (c instanceof LayoutManager) { + LayoutManager m = (LayoutManager) c; + if (m.getWidthModifier() != null && m.getWidthModifier().hasWeight()) { + return true; + } + } + } + return false; + } + + protected boolean childrenHaveVerticalWeights() { + for (Component c : mChildrenComponents) { + if (c instanceof LayoutManager) { + LayoutManager m = (LayoutManager) c; + if (m.getHeightModifier() != null && m.getHeightModifier().hasWeight()) { + return true; + } + } + } + return false; + } + + public boolean isInHorizontalFill() { + return mWidthModifier.isFill(); + } + + public boolean isInVerticalFill() { + return mHeightModifier.isFill(); + } + /** Base implementation of the measure resolution */ @Override public void measure( @@ -70,7 +104,7 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl float maxWidth, float minHeight, float maxHeight, - MeasurePass measure) { + @NonNull MeasurePass measure) { boolean hasWrap = true; float measuredWidth = Math.min(maxWidth, computeModifierDefinedWidth() - mMarginLeft - mMarginRight); @@ -87,7 +121,7 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl } else { hasWrap = false; } - if (mWidthModifier.isFill()) { + if (isInHorizontalFill()) { measuredWidth = insetMaxWidth; } else if (mWidthModifier.hasWeight()) { measuredWidth = Math.max(measuredWidth, computeModifierDefinedWidth()); @@ -95,7 +129,7 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl measuredWidth = Math.max(measuredWidth, minWidth); measuredWidth = Math.min(measuredWidth, insetMaxWidth); } - if (mHeightModifier.isFill()) { + if (isInVerticalFill()) { measuredHeight = insetMaxHeight; } else if (mHeightModifier.hasWeight()) { measuredHeight = Math.max(measuredHeight, computeModifierDefinedHeight()); @@ -136,7 +170,7 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl /** basic layout of internal components */ @Override - public void layout(RemoteContext context, MeasurePass measure) { + public void layout(@NonNull RemoteContext context, @NonNull MeasurePass measure) { super.layout(context, measure); ComponentMeasure self = measure.get(this); @@ -153,7 +187,7 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl * @param context * @param measure */ - public void selfLayout(RemoteContext context, MeasurePass measure) { + public void selfLayout(@NonNull RemoteContext context, @NonNull MeasurePass measure) { super.layout(context, measure); ComponentMeasure self = measure.get(this); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java index b29a05c27e5f..a366dc804758 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations.layout.manager import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -85,6 +87,7 @@ public class RowLayout extends LayoutManager implements ComponentStartOperation spacedBy); } + @NonNull @Override public String toString() { return "ROW [" @@ -103,14 +106,24 @@ public class RowLayout extends LayoutManager implements ComponentStartOperation + mVisibility; } + @NonNull @Override protected String getSerializedName() { return "ROW"; } @Override + public boolean isInHorizontalFill() { + return super.isInHorizontalFill() || childrenHaveHorizontalWeights(); + } + + @Override public void computeWrapSize( - PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) { + PaintContext context, + float maxWidth, + float maxHeight, + @NonNull MeasurePass measure, + @NonNull Size size) { DebugLog.s(() -> "COMPUTE WRAP SIZE in " + this + " (" + mComponentId + ")"); // int visibleChildrens = 0; for (Component c : mChildrenComponents) { @@ -135,7 +148,7 @@ public class RowLayout extends LayoutManager implements ComponentStartOperation float maxWidth, float minHeight, float maxHeight, - MeasurePass measure) { + @NonNull MeasurePass measure) { DebugLog.s(() -> "COMPUTE SIZE in " + this + " (" + mComponentId + ")"); float mw = maxWidth; for (Component child : mChildrenComponents) { @@ -149,7 +162,7 @@ public class RowLayout extends LayoutManager implements ComponentStartOperation } @Override - public void internalLayoutMeasure(PaintContext context, MeasurePass measure) { + public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) { ComponentMeasure selfMeasure = measure.get(this); DebugLog.s( () -> @@ -305,6 +318,7 @@ public class RowLayout extends LayoutManager implements ComponentStartOperation DebugLog.e(); } + @NonNull public static String name() { return "RowLayout"; } @@ -314,7 +328,7 @@ public class RowLayout extends LayoutManager implements ComponentStartOperation } public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, int componentId, int animationId, int horizontalPositioning, @@ -328,7 +342,7 @@ public class RowLayout extends LayoutManager implements ComponentStartOperation buffer.writeFloat(spacedBy); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int componentId = buffer.readInt(); int animationId = buffer.readInt(); int horizontalPositioning = buffer.readInt(); @@ -344,7 +358,7 @@ public class RowLayout extends LayoutManager implements ComponentStartOperation spacedBy)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", id(), name()) .description( "Row layout implementation, positioning components one" @@ -377,7 +391,7 @@ public class RowLayout extends LayoutManager implements ComponentStartOperation } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply( buffer, mComponentId, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java index b5c728135f76..e47ffdebb253 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout.managers; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; @@ -50,10 +52,10 @@ public class StateLayout extends LayoutManager implements ComponentStartOperatio // This keep track of all the components associated with a given Id, // (the key being the id), and the set of components corresponds to the set of states // TODO: we should be able to optimize this - public Map<Integer, Component[]> statePaintedComponents = new HashMap<>(); + @NonNull public Map<Integer, Component[]> statePaintedComponents = new HashMap<>(); public int MAX_CACHE_ELEMENTS = 16; - public int[] cacheListElementsId = new int[MAX_CACHE_ELEMENTS]; + @NonNull public int[] cacheListElementsId = new int[MAX_CACHE_ELEMENTS]; public boolean inTransition = false; @@ -168,7 +170,7 @@ public class StateLayout extends LayoutManager implements ComponentStartOperatio } @Override - public void layout(RemoteContext context, MeasurePass measure) { + public void layout(@NonNull RemoteContext context, @NonNull MeasurePass measure) { ComponentMeasure self = measure.get(this); super.selfLayout(context, measure); @@ -207,12 +209,12 @@ public class StateLayout extends LayoutManager implements ComponentStartOperatio @Override public void measure( - PaintContext context, + @NonNull PaintContext context, float minWidth, float maxWidth, float minHeight, float maxHeight, - MeasurePass measure) { + @NonNull MeasurePass measure) { // The general approach for this widget is to do most of the work/setup in measure. // layout and paint then simply use what's been setup in the measure phase. @@ -364,19 +366,20 @@ public class StateLayout extends LayoutManager implements ComponentStartOperatio } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { if (mIndexId != 0) { int newValue = context.getContext().mRemoteComposeState.getInteger(mIndexId); if (newValue != currentLayoutIndex) { previousLayoutIndex = currentLayoutIndex; currentLayoutIndex = newValue; inTransition = true; - System.out.println("currentLayout index is $currentLayoutIndex"); + // System.out.println("currentLayout index is $currentLayoutIndex"); // executeValueSetActions(getLayout(currentLayoutIndex)); invalidateMeasure(); } } - System.out.println("PAINTING LAYOUT STATELAYOUT, CURRENT INDEX " + currentLayoutIndex); + // System.out.println("PAINTING LAYOUT STATELAYOUT, CURRENT INDEX " + + // currentLayoutIndex); // Make sure to mark any components that are not in either the current or previous layout // as being GONE. int index = 0; @@ -529,6 +532,7 @@ public class StateLayout extends LayoutManager implements ComponentStartOperatio // } // } + @NonNull @Override public String toString() { return "STATE_LAYOUT"; @@ -539,7 +543,7 @@ public class StateLayout extends LayoutManager implements ComponentStartOperatio // } public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, int componentId, int animationId, int horizontalPositioning, @@ -553,7 +557,7 @@ public class StateLayout extends LayoutManager implements ComponentStartOperatio buffer.writeInt(indexId); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int componentId = buffer.readInt(); int animationId = buffer.readInt(); buffer.readInt(); // horizontalPositioning diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java index c1cabcd09c39..8aa7712635fc 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations.layout.manager import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -44,22 +46,24 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation private int mFontStyle = 0; private float mFontWeight = 400f; private int mFontFamilyId = -1; + private int mTextAlign = -1; private int mType = -1; private float mTextX; private float mTextY; + private float mTextW; private String mCachedString = ""; @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (mTextId != -1) { context.listensTo(mTextId, this); } } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { mCachedString = context.getText(mTextId); if (mType == -1) { if (mFontFamilyId != -1) { @@ -97,7 +101,8 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation float fontSize, int fontStyle, float fontWeight, - int fontFamilyId) { + int fontFamilyId, + int textAlign) { super(parent, componentId, animationId, x, y, width, height); mTextId = textId; mColor = color; @@ -105,6 +110,7 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation mFontStyle = fontStyle; mFontWeight = fontWeight; mFontFamilyId = fontFamilyId; + mTextAlign = textAlign; } public TextLayout( @@ -116,7 +122,8 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation float fontSize, int fontStyle, float fontWeight, - int fontFamilyId) { + int fontFamilyId, + int textAlign) { this( parent, componentId, @@ -130,13 +137,14 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation fontSize, fontStyle, fontWeight, - fontFamilyId); + fontFamilyId, + textAlign); } - public PaintBundle mPaint = new PaintBundle(); + @NonNull public PaintBundle mPaint = new PaintBundle(); @Override - public void paintingComponent(PaintContext context) { + public void paintingComponent(@NonNull PaintContext context) { context.save(); context.translate(mX, mY); mComponentModifiers.paint(context); @@ -176,6 +184,7 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation context.restore(); } + @NonNull @Override public String toString() { return "TEXT_LAYOUT [" @@ -194,13 +203,14 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation + mVisibility; } + @NonNull @Override protected String getSerializedName() { return "TEXT_LAYOUT"; } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append( indent, getSerializedName() @@ -228,7 +238,11 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation @Override public void computeWrapSize( - PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) { + @NonNull PaintContext context, + float maxWidth, + float maxHeight, + MeasurePass measure, + @NonNull Size size) { context.savePaint(); mPaint.reset(); mPaint.setTextSize(mFontSize); @@ -244,8 +258,10 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation mTextX = -bounds[0]; size.setHeight(h); mTextY = -bounds[1]; + mTextW = w; } + @NonNull public static String name() { return "TextLayout"; } @@ -255,7 +271,7 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation } public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, int componentId, int animationId, int textId, @@ -263,7 +279,8 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation float fontSize, int fontStyle, float fontWeight, - int fontFamilyId) { + int fontFamilyId, + int textAlign) { buffer.start(id()); buffer.writeInt(componentId); buffer.writeInt(animationId); @@ -273,9 +290,10 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation buffer.writeInt(fontStyle); buffer.writeFloat(fontWeight); buffer.writeInt(fontFamilyId); + buffer.writeInt(textAlign); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int componentId = buffer.readInt(); int animationId = buffer.readInt(); int textId = buffer.readInt(); @@ -284,6 +302,7 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation int fontStyle = buffer.readInt(); float fontWeight = buffer.readFloat(); int fontFamilyId = buffer.readInt(); + int textAlign = buffer.readInt(); operations.add( new TextLayout( null, @@ -294,10 +313,11 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation fontSize, fontStyle, fontWeight, - fontFamilyId)); + fontFamilyId, + textAlign)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", id(), name()) .description("Text layout implementation.\n\n") .field(INT, "COMPONENT_ID", "unique id for this component") @@ -313,7 +333,7 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply( buffer, mComponentId, @@ -323,6 +343,7 @@ public class TextLayout extends LayoutManager implements ComponentStartOperation mFontSize, mFontStyle, mFontWeight, - mFontFamilyId); + mFontFamilyId, + mTextAlign); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java index 285425f99765..426e02337f5b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout.measure; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.operations.layout.Component; /** Encapsulate the result of a measure pass for a component */ @@ -80,7 +82,7 @@ public class ComponentMeasure { this(id, x, y, w, h, Component.Visibility.VISIBLE); } - public ComponentMeasure(Component component) { + public ComponentMeasure(@NonNull Component component) { this( component.getComponentId(), component.getX(), @@ -90,7 +92,7 @@ public class ComponentMeasure { component.mVisibility); } - public void copyFrom(ComponentMeasure m) { + public void copyFrom(@NonNull ComponentMeasure m) { mX = m.mX; mY = m.mY; mW = m.mW; @@ -98,7 +100,7 @@ public class ComponentMeasure { mVisibility = m.mVisibility; } - public boolean same(ComponentMeasure m) { + public boolean same(@NonNull ComponentMeasure m) { return mX == m.mX && mY == m.mY && mW == m.mW && mH == m.mH && mVisibility == m.mVisibility; } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java index 8d01fea03690..112ab1b93474 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout.measure; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.operations.layout.Component; import java.util.HashMap; @@ -24,13 +26,13 @@ import java.util.HashMap; * array vs the current hashmap */ public class MeasurePass { - HashMap<Integer, ComponentMeasure> mList = new HashMap<>(); + @NonNull HashMap<Integer, ComponentMeasure> mList = new HashMap<>(); public void clear() { mList.clear(); } - public void add(ComponentMeasure measure) throws Exception { + public void add(@NonNull ComponentMeasure measure) throws Exception { if (measure.mId == -1) { throw new Exception("Component has no id!"); } @@ -41,7 +43,7 @@ public class MeasurePass { return mList.containsKey(id); } - public ComponentMeasure get(Component c) { + public ComponentMeasure get(@NonNull Component c) { if (!mList.containsKey(c.getComponentId())) { ComponentMeasure measure = new ComponentMeasure( diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java index 64e40f7c591c..76a97ca0eb51 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations.layout.modifie import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -42,7 +44,7 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation { float mA; int mShapeType = ShapeType.RECTANGLE; - public PaintBundle mPaint = new PaintBundle(); + @NonNull public PaintBundle mPaint = new PaintBundle(); public BackgroundModifierOperation( float x, @@ -66,12 +68,12 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mX, mY, mWidth, mHeight, mR, mG, mB, mA, mShapeType); } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append( indent, "BACKGROUND = [" @@ -101,11 +103,13 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation { this.mHeight = height; } + @NonNull @Override public String toString() { return "BackgroundModifierOperation(" + mWidth + " x " + mHeight + ")"; } + @NonNull public static String name() { return CLASS_NAME; } @@ -115,7 +119,7 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation { } public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, float x, float y, float width, @@ -138,7 +142,7 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation { buffer.writeInt(shapeType); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float x = buffer.readFloat(); float y = buffer.readFloat(); float width = buffer.readFloat(); @@ -153,7 +157,7 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.savePaint(); mPaint.reset(); mPaint.setStyle(PaintBundle.STYLE_FILL); @@ -167,7 +171,7 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation { context.restorePaint(); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) .description("define the Background Modifier") .field(FLOAT, "x", "") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java index 92c0a733d8a1..d48a9c732cd5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations.layout.modifie import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -45,7 +47,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation { float mA; int mShapeType = ShapeType.RECTANGLE; - public PaintBundle paint = new PaintBundle(); + @NonNull public PaintBundle paint = new PaintBundle(); public BorderModifierOperation( float x, @@ -73,7 +75,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation { } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append( indent, "BORDER = [" @@ -105,7 +107,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply( buffer, mX, @@ -127,6 +129,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation { this.mHeight = height; } + @NonNull @Override public String toString() { return "BorderModifierOperation(" @@ -152,6 +155,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation { + ")"; } + @NonNull public static String name() { return CLASS_NAME; } @@ -161,7 +165,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation { } public static void apply( - WireBuffer buffer, + @NonNull WireBuffer buffer, float x, float y, float width, @@ -188,7 +192,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation { buffer.writeInt(shapeType); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float x = buffer.readFloat(); float y = buffer.readFloat(); float width = buffer.readFloat(); @@ -206,7 +210,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation { } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.savePaint(); paint.reset(); paint.setColor(mR, mG, mB, mA); @@ -225,7 +229,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation { context.restorePaint(); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) .description("define the Border Modifier") .field(FLOAT, "x", "") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java index 0d8aeaa2f06a..78b51c3f83aa 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java @@ -15,14 +15,14 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout.modifiers; -import com.android.internal.widget.remotecompose.core.CoreDocument; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; -import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; import java.util.List; @@ -35,7 +35,7 @@ public class ClipRectModifierOperation extends DecoratorModifierOperation { float mHeight; @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.clipRect(0f, 0f, mWidth, mHeight); } @@ -46,21 +46,16 @@ public class ClipRectModifierOperation extends DecoratorModifierOperation { } @Override - public void onClick( - RemoteContext context, CoreDocument document, Component component, float x, float y) { - // nothing - } - - @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append(indent, "CLIP_RECT = [" + mWidth + ", " + mHeight + "]"); } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer); } + @NonNull public static String name() { return CLASS_NAME; } @@ -69,15 +64,15 @@ public class ClipRectModifierOperation extends DecoratorModifierOperation { return OP_CODE; } - public static void apply(WireBuffer buffer) { + public static void apply(@NonNull WireBuffer buffer) { buffer.start(OP_CODE); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(WireBuffer buffer, @NonNull List<Operation> operations) { operations.add(new ClipRectModifierOperation()); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Canvas Operations", OP_CODE, CLASS_NAME) .description("Draw the specified round-rect"); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java index 95786a8b62b0..011d7edd18c8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout.modifiers; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.PaintOperation; @@ -22,29 +24,34 @@ import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.operations.MatrixRestore; import com.android.internal.widget.remotecompose.core.operations.MatrixSave; +import com.android.internal.widget.remotecompose.core.operations.layout.ClickHandler; import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent; +import com.android.internal.widget.remotecompose.core.operations.layout.TouchHandler; import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; import java.util.ArrayList; /** Maintain a list of modifiers */ -public class ComponentModifiers extends PaintOperation implements DecoratorComponent { - ArrayList<ModifierOperation> mList = new ArrayList<>(); +public class ComponentModifiers extends PaintOperation + implements DecoratorComponent, ClickHandler, TouchHandler { + @NonNull ArrayList<ModifierOperation> mList = new ArrayList<>(); + @NonNull public ArrayList<ModifierOperation> getList() { return mList; } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { super.apply(context); for (ModifierOperation op : mList) { op.apply(context); } } + @NonNull @Override public String toString() { String str = "ComponentModifiers \n"; @@ -59,7 +66,7 @@ public class ComponentModifiers extends PaintOperation implements DecoratorCompo // nothing } - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append(indent, "MODIFIERS"); for (ModifierOperation m : mList) { m.serializeToString(indent + 1, serializer); @@ -75,7 +82,7 @@ public class ComponentModifiers extends PaintOperation implements DecoratorCompo } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { float tx = 0f; float ty = 0f; for (ModifierOperation op : mList) { @@ -127,8 +134,38 @@ public class ComponentModifiers extends PaintOperation implements DecoratorCompo public void onClick( RemoteContext context, CoreDocument document, Component component, float x, float y) { for (ModifierOperation op : mList) { - if (op instanceof DecoratorComponent) { - ((DecoratorComponent) op).onClick(context, document, component, x, y); + if (op instanceof ClickHandler) { + ((ClickHandler) op).onClick(context, document, component, x, y); + } + } + } + + @Override + public void onTouchDown( + RemoteContext context, CoreDocument document, Component component, float x, float y) { + for (ModifierOperation op : mList) { + if (op instanceof TouchHandler) { + ((TouchHandler) op).onTouchDown(context, document, component, x, y); + } + } + } + + @Override + public void onTouchUp( + RemoteContext context, CoreDocument document, Component component, float x, float y) { + for (ModifierOperation op : mList) { + if (op instanceof TouchHandler) { + ((TouchHandler) op).onTouchUp(context, document, component, x, y); + } + } + } + + @Override + public void onTouchCancel( + RemoteContext context, CoreDocument document, Component component, float x, float y) { + for (ModifierOperation op : mList) { + if (op instanceof TouchHandler) { + ((TouchHandler) op).onTouchCancel(context, document, component, x, y); } } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java index 312d016029fb..26e737b32027 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java @@ -17,7 +17,9 @@ package com.android.internal.widget.remotecompose.core.operations.layout.modifie import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; -import com.android.internal.widget.remotecompose.core.CoreDocument; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -37,49 +39,52 @@ public class ComponentVisibilityOperation private static final int OP_CODE = Operations.MODIFIER_VISIBILITY; int mVisibilityId; - Component.Visibility mVisibility = Component.Visibility.VISIBLE; + @NonNull Component.Visibility mVisibility = Component.Visibility.VISIBLE; private LayoutComponent mParent; public ComponentVisibilityOperation(int id) { mVisibilityId = id; } + @NonNull @Override public String toString() { return "ComponentVisibilityOperation(" + mVisibilityId + ")"; } + @NonNull public String serializedName() { return "COMPONENT_VISIBILITY"; } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append(indent, serializedName() + " = " + mVisibilityId); } @Override public void apply(RemoteContext context) {} + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } @Override public void write(WireBuffer buffer) {} - public static void apply(WireBuffer buffer, int valueId) { + public static void apply(@NonNull WireBuffer buffer, int valueId) { buffer.start(OP_CODE); buffer.writeInt(valueId); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int valueId = buffer.readInt(); operations.add(new ComponentVisibilityOperation(valueId)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", OP_CODE, "ComponentVisibility") .description( "This operation allows setting a component" @@ -88,12 +93,12 @@ public class ComponentVisibilityOperation } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { context.listensTo(mVisibilityId, this); } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { int visibility = context.getInteger(mVisibilityId); if (visibility == Component.Visibility.VISIBLE.ordinal()) { mVisibility = Component.Visibility.VISIBLE; @@ -115,8 +120,4 @@ public class ComponentVisibilityOperation @Override public void layout(RemoteContext context, float width, float height) {} - - @Override - public void onClick( - RemoteContext context, CoreDocument document, Component component, float x, float y) {} } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java index 41e18cbafbe6..b4c4108802ee 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java @@ -15,10 +15,7 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout.modifiers; -import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.PaintOperation; -import com.android.internal.widget.remotecompose.core.RemoteContext; -import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent; /** @@ -26,11 +23,4 @@ import com.android.internal.widget.remotecompose.core.operations.layout.Decorato * output (background, border...) */ public abstract class DecoratorModifierOperation extends PaintOperation - implements ModifierOperation, DecoratorComponent { - - @Override - public void onClick( - RemoteContext context, CoreDocument document, Component component, float x, float y) { - // nothing - } -} + implements ModifierOperation, DecoratorComponent {} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java index 408bebcfb7d9..3c2d85cfee5b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout.modifiers; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.VariableSupport; import com.android.internal.widget.remotecompose.core.operations.Utils; @@ -29,8 +32,10 @@ public abstract class DimensionModifierOperation implements ModifierOperation, V WRAP, WEIGHT, INTRINSIC_MIN, - INTRINSIC_MAX; + INTRINSIC_MAX, + EXACT_DP; + @NonNull static Type fromInt(int value) { switch (value) { case 0: @@ -45,6 +50,8 @@ public abstract class DimensionModifierOperation implements ModifierOperation, V return INTRINSIC_MIN; case 5: return INTRINSIC_MAX; + case 6: + return EXACT_DP; } return EXACT; } @@ -68,19 +75,32 @@ public abstract class DimensionModifierOperation implements ModifierOperation, V } @Override - public void updateVariables(RemoteContext context) { + public void updateVariables(@NonNull RemoteContext context) { if (mType == Type.EXACT) { mOutValue = Float.isNaN(mValue) ? context.getFloat(Utils.idFromNan(mValue)) : mValue; } + if (mType == Type.EXACT_DP) { + float pre = mOutValue; + mOutValue = Float.isNaN(mValue) ? context.getFloat(Utils.idFromNan(mValue)) : mValue; + mOutValue *= context.getDensity(); + if (pre != mOutValue) { + context.getDocument().getRootLayoutComponent().invalidateMeasure(); + } + } } @Override - public void registerListening(RemoteContext context) { + public void registerListening(@NonNull RemoteContext context) { if (mType == Type.EXACT) { if (Float.isNaN(mValue)) { context.listensTo(Utils.idFromNan(mValue), this); } } + if (mType == Type.EXACT_DP) { + if (Float.isNaN(mValue)) { + context.listensTo(Utils.idFromNan(mValue), this); + } + } } public boolean hasWeight() { @@ -107,25 +127,31 @@ public abstract class DimensionModifierOperation implements ModifierOperation, V mOutValue = mValue = value; } + @NonNull public String serializedName() { return "DIMENSION"; } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { if (mType == Type.EXACT) { serializer.append(indent, serializedName() + " = " + mValue); } + if (mType == Type.EXACT_DP) { + serializer.append(indent, serializedName() + " = " + mValue + " dp"); + } } @Override public void apply(RemoteContext context) {} + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } + @NonNull @Override public String toString() { return "DimensionModifierOperation(" + mValue + ")"; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java new file mode 100644 index 000000000000..2b3038281d57 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java @@ -0,0 +1,302 @@ +/* + * 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.internal.widget.remotecompose.core.operations.layout.modifiers; + +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.operations.layout.AnimatableValue; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; + +import java.util.List; + +/** + * Represents a padding modifier. Padding modifiers can be chained and will impact following + * modifiers. + */ +public class GraphicsLayerModifierOperation extends DecoratorModifierOperation { + private static final int OP_CODE = Operations.MODIFIER_GRAPHICS_LAYER; + public static final String CLASS_NAME = "GraphicsLayerModifierOperation"; + + AnimatableValue mScaleX; + AnimatableValue mScaleY; + AnimatableValue mRotationX; + AnimatableValue mRotationY; + AnimatableValue mRotationZ; + AnimatableValue mTransformOriginX; + AnimatableValue mTransformOriginY; + AnimatableValue mShadowElevation; + AnimatableValue mAlpha; + AnimatableValue mCameraDistance; + int mBlendMode; + int mSpotShadowColorId; + int mAmbientShadowColorId; + int mColorFilterId; + int mRenderEffectId; + + public GraphicsLayerModifierOperation( + float scaleX, + float scaleY, + float rotationX, + float rotationY, + float rotationZ, + float shadowElevation, + float transformOriginX, + float transformOriginY, + float alpha, + float cameraDistance, + int blendMode, + int spotShadowColorId, + int ambientShadowColorId, + int colorFilterId, + int renderEffectId) { + mScaleX = new AnimatableValue(scaleX); + mScaleY = new AnimatableValue(scaleY); + mRotationX = new AnimatableValue(rotationX); + mRotationY = new AnimatableValue(rotationY); + mRotationZ = new AnimatableValue(rotationZ); + mShadowElevation = new AnimatableValue(shadowElevation); + mTransformOriginX = new AnimatableValue(transformOriginX); + mTransformOriginY = new AnimatableValue(transformOriginY); + mAlpha = new AnimatableValue(alpha); + mCameraDistance = new AnimatableValue(cameraDistance); + mBlendMode = blendMode; + mSpotShadowColorId = spotShadowColorId; + mAmbientShadowColorId = ambientShadowColorId; + mColorFilterId = colorFilterId; + mRenderEffectId = renderEffectId; + } + + public float getScaleX() { + return mScaleX.getValue(); + } + + public float getScaleY() { + return mScaleY.getValue(); + } + + public float getRotationX() { + return mRotationX.getValue(); + } + + public float getRotationY() { + return mRotationY.getValue(); + } + + public float getRotationZ() { + return mRotationZ.getValue(); + } + + public float getShadowElevation() { + return mShadowElevation.getValue(); + } + + public float getTransformOriginX() { + return mTransformOriginX.getValue(); + } + + public float getTransformOriginY() { + return mTransformOriginY.getValue(); + } + + public float getAlpha() { + return mAlpha.getValue(); + } + + public float getCameraDistance() { + return mCameraDistance.getValue(); + } + + // TODO: add implementation for blendmode + public int getBlendModeId() { + return mBlendMode; + } + + // TODO: add implementation for shadow + public int getSpotShadowColorId() { + return mSpotShadowColorId; + } + + public int getAmbientShadowColorId() { + return mAmbientShadowColorId; + } + + // TODO: add implementation for color filters + public int getColorFilterId() { + return mColorFilterId; + } + + public int getRenderEffectId() { + return mRenderEffectId; + } + + @Override + public void write(WireBuffer buffer) { + apply( + buffer, + mScaleX.getValue(), + mScaleY.getValue(), + mRotationX.getValue(), + mRotationY.getValue(), + mRotationZ.getValue(), + mShadowElevation.getValue(), + mTransformOriginX.getValue(), + mTransformOriginY.getValue(), + mAlpha.getValue(), + mCameraDistance.getValue(), + mBlendMode, + mSpotShadowColorId, + mAmbientShadowColorId, + mColorFilterId, + mRenderEffectId); + } + + @Override + public void serializeToString(int indent, StringSerializer serializer) { + serializer.append(indent, "GRAPHICS_LAYER = [" + mScaleX + ", " + mScaleY + "]"); + } + + @Override + public String deepToString(String indent) { + return (indent != null ? indent : "") + toString(); + } + + @Override + public void paint(PaintContext context) { + mScaleX.evaluate(context); + mScaleY.evaluate(context); + mRotationX.evaluate(context); + mRotationY.evaluate(context); + mRotationZ.evaluate(context); + mTransformOriginX.evaluate(context); + mTransformOriginY.evaluate(context); + mShadowElevation.evaluate(context); + mAlpha.evaluate(context); + mCameraDistance.evaluate(context); + } + + @Override + public String toString() { + return "GraphicsLayerModifierOperation(" + mScaleX + ", " + mScaleY + ")"; + } + + public static String name() { + return CLASS_NAME; + } + + public static int id() { + return OP_CODE; + } + + public static void apply( + WireBuffer buffer, + float scaleX, + float scaleY, + float rotationX, + float rotationY, + float rotationZ, + float shadowElevation, + float transformOriginX, + float transformOriginY, + float alpha, + float cameraDistance, + int blendMode, + int spotShadowColorId, + int ambientShadowColorId, + int colorFilterId, + int renderEffectId) { + buffer.start(OP_CODE); + buffer.writeFloat(scaleX); + buffer.writeFloat(scaleY); + buffer.writeFloat(rotationX); + buffer.writeFloat(rotationY); + buffer.writeFloat(rotationZ); + buffer.writeFloat(shadowElevation); + buffer.writeFloat(transformOriginX); + buffer.writeFloat(transformOriginY); + buffer.writeFloat(alpha); + buffer.writeFloat(cameraDistance); + buffer.writeInt(blendMode); + buffer.writeInt(spotShadowColorId); + buffer.writeInt(ambientShadowColorId); + buffer.writeInt(colorFilterId); + buffer.writeInt(renderEffectId); + } + + public static void read(WireBuffer buffer, List<Operation> operations) { + float scaleX = buffer.readFloat(); + float scaleY = buffer.readFloat(); + float rotationX = buffer.readFloat(); + float rotationY = buffer.readFloat(); + float rotationZ = buffer.readFloat(); + float shadowElevation = buffer.readFloat(); + float transformOriginX = buffer.readFloat(); + float transformOriginY = buffer.readFloat(); + float alpha = buffer.readFloat(); + float cameraDistance = buffer.readFloat(); + int blendMode = buffer.readInt(); + int spotShadowColorId = buffer.readInt(); + int ambientShadowColorId = buffer.readInt(); + int colorFilterId = buffer.readInt(); + int renderEffectId = buffer.readInt(); + operations.add( + new GraphicsLayerModifierOperation( + scaleX, + scaleY, + rotationX, + rotationY, + rotationZ, + shadowElevation, + transformOriginX, + transformOriginY, + alpha, + cameraDistance, + blendMode, + spotShadowColorId, + ambientShadowColorId, + colorFilterId, + renderEffectId)); + } + + public static void documentation(DocumentationBuilder doc) { + doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) + .description("define the GraphicsLayer Modifier") + .field(FLOAT, "scaleX", "") + .field(FLOAT, "scaleY", "") + .field(FLOAT, "rotationX", "") + .field(FLOAT, "rotationY", "") + .field(FLOAT, "rotationZ", "") + .field(FLOAT, "shadowElevation", "") + .field(FLOAT, "transformOriginX", "") + .field(FLOAT, "transformOriginY", "") + .field(FLOAT, "alpha", "") + .field(FLOAT, "cameraDistance", "") + .field(INT, "blendMode", "") + .field(INT, "spotShadowColorId", "") + .field(INT, "ambientShadowColorId", "") + .field(INT, "colorFilterId", "") + .field(INT, "renderEffectId", ""); + } + + @Override + public void layout(RemoteContext context, float width, float height) {} +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java index d3613f844981..97c76c0aedf7 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations.layout.modifie import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.WireBuffer; @@ -30,6 +32,7 @@ public class HeightModifierOperation extends DimensionModifierOperation { private static final int OP_CODE = Operations.MODIFIER_HEIGHT; public static final String CLASS_NAME = "HeightModifierOperation"; + @NonNull public static String name() { return CLASS_NAME; } @@ -38,13 +41,13 @@ public class HeightModifierOperation extends DimensionModifierOperation { return OP_CODE; } - public static void apply(WireBuffer buffer, int type, float value) { + public static void apply(@NonNull WireBuffer buffer, int type, float value) { buffer.start(OP_CODE); buffer.writeInt(type); buffer.writeFloat(value); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Type type = Type.fromInt(buffer.readInt()); float value = buffer.readFloat(); Operation op = new HeightModifierOperation(type, value); @@ -52,7 +55,7 @@ public class HeightModifierOperation extends DimensionModifierOperation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mType.ordinal(), mValue); } @@ -68,17 +71,19 @@ public class HeightModifierOperation extends DimensionModifierOperation { super(value); } + @NonNull @Override public String toString() { - return "Height(" + mValue + ")"; + return "Height(" + mType + ", " + mValue + ")"; } + @NonNull @Override public String serializedName() { return "HEIGHT"; } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) .description("define the animation") .field(INT, "type", "") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java index ac42470a6f8f..836321fff8e6 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java @@ -17,6 +17,9 @@ package com.android.internal.widget.remotecompose.core.operations.layout.modifie import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; @@ -39,6 +42,7 @@ public class HostActionOperation implements ActionOperation { mActionId = id; } + @NonNull @Override public String toString() { return "HostActionOperation(" + mActionId + ")"; @@ -48,20 +52,22 @@ public class HostActionOperation implements ActionOperation { return mActionId; } + @NonNull public String serializedName() { return "HOST_ACTION"; } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append(indent, serializedName() + " = " + mActionId); } @Override public void apply(RemoteContext context) {} + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } @@ -70,21 +76,25 @@ public class HostActionOperation implements ActionOperation { @Override public void runAction( - RemoteContext context, CoreDocument document, Component component, float x, float y) { + @NonNull RemoteContext context, + CoreDocument document, + Component component, + float x, + float y) { context.runAction(mActionId, ""); } - public static void apply(WireBuffer buffer, int actionId) { + public static void apply(@NonNull WireBuffer buffer, int actionId) { buffer.start(OP_CODE); buffer.writeInt(actionId); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int actionId = buffer.readInt(); operations.add(new HostActionOperation(actionId)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", OP_CODE, "HostAction") .description("Host action. This operation represents a host action") .field(INT, "ACTION_ID", "Host Action ID"); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java index b674a582fc14..e97e89784a23 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java @@ -17,6 +17,9 @@ package com.android.internal.widget.remotecompose.core.operations.layout.modifie import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; @@ -33,31 +36,47 @@ import java.util.List; public class HostNamedActionOperation implements ActionOperation { private static final int OP_CODE = Operations.HOST_NAMED_ACTION; + public static final int FLOAT_TYPE = 0; + public static final int INT_TYPE = 1; + public static final int STRING_TYPE = 2; + public static final int NONE_TYPE = -1; + int mTextId = -1; + int mType = NONE_TYPE; + int mValueId = -1; - public HostNamedActionOperation(int id) { + public HostNamedActionOperation(int id, int type, int valueId) { mTextId = id; + mType = type; + mValueId = valueId; } + @NonNull @Override public String toString() { - return "HostNamedActionOperation(" + mTextId + ")"; + return "HostNamedActionOperation(" + mTextId + " : " + mValueId + ")"; } + @NonNull public String serializedName() { return "HOST_NAMED_ACTION"; } @Override - public void serializeToString(int indent, StringSerializer serializer) { - serializer.append(indent, serializedName() + " = " + mTextId); + public void serializeToString(int indent, @NonNull StringSerializer serializer) { + if (mValueId != -1) { + serializer.append(indent, serializedName() + " = " + mTextId + " : " + mValueId); + } else { + serializer.append(indent, serializedName() + " = " + mTextId); + } } @Override public void apply(RemoteContext context) {} + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } @@ -66,23 +85,42 @@ public class HostNamedActionOperation implements ActionOperation { @Override public void runAction( - RemoteContext context, CoreDocument document, Component component, float x, float y) { - context.runNamedAction(mTextId); + @NonNull RemoteContext context, + CoreDocument document, + Component component, + float x, + float y) { + Object value = null; + if (mValueId != -1) { + if (mType == INT_TYPE) { + value = context.mRemoteComposeState.getInteger(mValueId); + } else if (mType == STRING_TYPE) { + value = context.mRemoteComposeState.getFromId(mValueId); + } else if (mType == FLOAT_TYPE) { + value = context.mRemoteComposeState.getFloat(mValueId); + } + } + context.runNamedAction(mTextId, value); } - public static void apply(WireBuffer buffer, int textId) { + public static void apply(@NonNull WireBuffer buffer, int textId, int type, int valueId) { buffer.start(OP_CODE); buffer.writeInt(textId); + buffer.writeInt(type); + buffer.writeInt(valueId); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int textId = buffer.readInt(); - operations.add(new HostNamedActionOperation(textId)); + int type = buffer.readInt(); + int valueId = buffer.readInt(); + operations.add(new HostNamedActionOperation(textId, type, valueId)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", OP_CODE, "HostNamedAction") .description("Host Named action. This operation represents a host action") - .field(INT, "TEXT_ID", "Named Host Action Text ID"); + .field(INT, "TEXT_ID", "Named Host Action Text ID") + .field(INT, "VALUE_ID", "Named Host Action Value ID"); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java new file mode 100644 index 000000000000..65fe345ac920 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java @@ -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. + */ +package com.android.internal.widget.remotecompose.core.operations.layout.modifiers; + +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.operations.Utils; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; + +import java.util.List; + +/** Represents an offset modifier. */ +public class OffsetModifierOperation extends DecoratorModifierOperation { + private static final int OP_CODE = Operations.MODIFIER_OFFSET; + public static final String CLASS_NAME = "OffsetModifierOperation"; + + float mX; + float mY; + + public OffsetModifierOperation(float x, float y) { + this.mX = x; + this.mY = y; + } + + public float getX() { + return mX; + } + + public float getY() { + return mY; + } + + public void setX(float x) { + this.mX = x; + } + + public void setY(float y) { + this.mY = y; + } + + @Override + public void write(WireBuffer buffer) { + apply(buffer, mX, mY); + } + + // @Override + public void serializeToString(int indent, StringSerializer serializer) { + serializer.append(indent, "OFFSET = [" + mX + ", " + mY + "]"); + } + + @Override + public String deepToString(String indent) { + return (indent != null ? indent : "") + toString(); + } + + @Override + public void paint(PaintContext context) { + float x = context.getContext().mRemoteComposeState.getFloat(Utils.idFromNan(mX)); + float y = context.getContext().mRemoteComposeState.getFloat(Utils.idFromNan(mY)); + float density = context.getContext().getDensity(); + x *= density; + y *= density; + context.translate(x, y); + } + + @Override + public String toString() { + return "OffsetModifierOperation(" + mX + ", " + mY + ")"; + } + + public static String name() { + return CLASS_NAME; + } + + public static int id() { + return OP_CODE; + } + + public static void apply(WireBuffer buffer, float x, float y) { + buffer.start(OP_CODE); + buffer.writeFloat(x); + buffer.writeFloat(y); + } + + public static void read(WireBuffer buffer, List<Operation> operations) { + float x = buffer.readFloat(); + float y = buffer.readFloat(); + operations.add(new OffsetModifierOperation(x, y)); + } + + public static void documentation(DocumentationBuilder doc) { + doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) + .description("define the Offset Modifier") + .field(FLOAT, "x", "") + .field(FLOAT, "y", ""); + } + + @Override + public void layout(RemoteContext context, float width, float height) {} +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java index e0ec1a60ef7e..ed5522ea865f 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java @@ -17,6 +17,9 @@ package com.android.internal.widget.remotecompose.core.operations.layout.modifie import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -78,12 +81,12 @@ public class PaddingModifierOperation implements ModifierOperation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mLeft, mTop, mRight, mBottom); } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append( indent, "PADDING = [" + mLeft + ", " + mTop + ", " + mRight + ", " + mBottom + "]"); } @@ -91,11 +94,13 @@ public class PaddingModifierOperation implements ModifierOperation { @Override public void apply(RemoteContext context) {} + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } + @NonNull @Override public String toString() { return "PaddingModifierOperation(" @@ -109,6 +114,7 @@ public class PaddingModifierOperation implements ModifierOperation { + ")"; } + @NonNull public static String name() { return CLASS_NAME; } @@ -117,7 +123,8 @@ public class PaddingModifierOperation implements ModifierOperation { return Operations.MODIFIER_PADDING; } - public static void apply(WireBuffer buffer, float left, float top, float right, float bottom) { + public static void apply( + @NonNull WireBuffer buffer, float left, float top, float right, float bottom) { buffer.start(Operations.MODIFIER_PADDING); buffer.writeFloat(left); buffer.writeFloat(top); @@ -125,7 +132,7 @@ public class PaddingModifierOperation implements ModifierOperation { buffer.writeFloat(bottom); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float left = buffer.readFloat(); float top = buffer.readFloat(); float right = buffer.readFloat(); @@ -133,7 +140,7 @@ public class PaddingModifierOperation implements ModifierOperation { operations.add(new PaddingModifierOperation(left, top, right, bottom)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) .description("define the Padding Modifier") .field(FLOAT, "left", "") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java index dc95fe7774aa..6218dd5f3311 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java @@ -17,7 +17,8 @@ package com.android.internal.widget.remotecompose.core.operations.layout.modifie import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; -import com.android.internal.widget.remotecompose.core.CoreDocument; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; @@ -25,7 +26,6 @@ import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; import com.android.internal.widget.remotecompose.core.operations.DrawBase4; -import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent; import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; @@ -37,7 +37,7 @@ public class RoundedClipRectModifierOperation extends DrawBase4 public static final int OP_CODE = Operations.MODIFIER_ROUNDED_CLIP_RECT; public static final String CLASS_NAME = "RoundedClipRectModifierOperation"; - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Maker m = RoundedClipRectModifierOperation::new; read(m, buffer, operations); } @@ -46,16 +46,17 @@ public class RoundedClipRectModifierOperation extends DrawBase4 return OP_CODE; } + @NonNull public static String name() { return CLASS_NAME; } @Override - protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) { + protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) { apply(buffer, v1, v2, v3, v4); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Modifier Operations", id(), "RoundedClipRectModifierOperation") .description("clip with rectangle") .field( @@ -90,7 +91,7 @@ public class RoundedClipRectModifierOperation extends DrawBase4 } @Override - public void paint(PaintContext context) { + public void paint(@NonNull PaintContext context) { context.roundedClipRect(mWidth, mHeight, mX1, mY1, mX2, mY2); } @@ -101,13 +102,7 @@ public class RoundedClipRectModifierOperation extends DrawBase4 } @Override - public void onClick( - RemoteContext context, CoreDocument document, Component component, float x, float y) { - // nothing - } - - @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append( indent, "ROUNDED_CLIP_RECT = [" @@ -135,7 +130,11 @@ public class RoundedClipRectModifierOperation extends DrawBase4 * @param bottomEnd bottomEnd radius */ public static void apply( - WireBuffer buffer, float topStart, float topEnd, float bottomStart, float bottomEnd) { + @NonNull WireBuffer buffer, + float topStart, + float topEnd, + float bottomStart, + float bottomEnd) { write(buffer, OP_CODE, topStart, topEnd, bottomStart, bottomEnd); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java new file mode 100644 index 000000000000..29ec82810a7c --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java @@ -0,0 +1,97 @@ +/* + * 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.internal.widget.remotecompose.core.operations.layout.modifiers; + +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; + +import com.android.internal.widget.remotecompose.core.CoreDocument; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.operations.layout.ActionOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.Component; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; + +import java.util.List; + +/** Apply a value change on an float variable. */ +public class ValueFloatChangeActionOperation implements ActionOperation { + private static final int OP_CODE = Operations.VALUE_FLOAT_CHANGE_ACTION; + + int mTargetValueId = -1; + float mValue = -1; + + public ValueFloatChangeActionOperation(int id, float value) { + mTargetValueId = id; + mValue = value; + } + + @Override + public String toString() { + return "ValueFloatChangeActionOperation(" + mTargetValueId + ")"; + } + + public String serializedName() { + return "VALUE_FLOAT_CHANGE"; + } + + @Override + public void serializeToString(int indent, StringSerializer serializer) { + serializer.append(indent, serializedName() + " = " + mTargetValueId + " -> " + mValue); + } + + @Override + public void apply(RemoteContext context) {} + + @Override + public String deepToString(String indent) { + return (indent != null ? indent : "") + toString(); + } + + @Override + public void write(WireBuffer buffer) {} + + @Override + public void runAction( + RemoteContext context, CoreDocument document, Component component, float x, float y) { + System.out.println("OVERRIDE " + mTargetValueId + " TO " + mValue); + context.overrideFloat(mTargetValueId, mValue); + } + + public static void apply(WireBuffer buffer, int valueId, float value) { + buffer.start(OP_CODE); + buffer.writeInt(valueId); + buffer.writeFloat(value); + } + + public static void read(WireBuffer buffer, List<Operation> operations) { + int valueId = buffer.readInt(); + float value = buffer.readFloat(); + operations.add(new ValueFloatChangeActionOperation(valueId, value)); + } + + public static void documentation(DocumentationBuilder doc) { + doc.operation("Layout Operations", OP_CODE, "ValueFloatChangeActionOperation") + .description( + "ValueIntegerChange action. " + + " This operation represents a value change for the given id") + .field(INT, "TARGET_VALUE_ID", "Value ID") + .field(FLOAT, "VALUE", "float value to be assigned to the target"); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java index 8876720c9990..d7ce8acc72bf 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java @@ -17,6 +17,9 @@ package com.android.internal.widget.remotecompose.core.operations.layout.modifie import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; @@ -41,25 +44,28 @@ public class ValueIntegerChangeActionOperation implements ActionOperation { mValue = value; } + @NonNull @Override public String toString() { return "ValueChangeActionOperation(" + mTargetValueId + ")"; } + @NonNull public String serializedName() { return "VALUE_INTEGER_CHANGE"; } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append(indent, serializedName() + " = " + mTargetValueId + " -> " + mValue); } @Override public void apply(RemoteContext context) {} + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } @@ -68,23 +74,27 @@ public class ValueIntegerChangeActionOperation implements ActionOperation { @Override public void runAction( - RemoteContext context, CoreDocument document, Component component, float x, float y) { + @NonNull RemoteContext context, + CoreDocument document, + Component component, + float x, + float y) { context.overrideInteger(mTargetValueId, mValue); } - public static void apply(WireBuffer buffer, int valueId, int value) { + public static void apply(@NonNull WireBuffer buffer, int valueId, int value) { buffer.start(OP_CODE); buffer.writeInt(valueId); buffer.writeInt(value); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int valueId = buffer.readInt(); int value = buffer.readInt(); operations.add(new ValueIntegerChangeActionOperation(valueId, value)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", OP_CODE, "ValueIntegerChangeActionOperation") .description( "ValueIntegerChange action. " diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java index fb5e911e8bc6..75d13e785d4c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java @@ -17,6 +17,9 @@ package com.android.internal.widget.remotecompose.core.operations.layout.modifie import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; @@ -41,17 +44,19 @@ public class ValueIntegerExpressionChangeActionOperation implements ActionOperat mValueExpressionId = value; } + @NonNull @Override public String toString() { return "ValueIntegerExpressionChangeActionOperation(" + mTargetValueId + ")"; } + @NonNull public String serializedName() { return "VALUE_INTEGER_EXPRESSION_CHANGE"; } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append( indent, serializedName() + " = " + mTargetValueId + " -> " + mValueExpressionId); } @@ -59,8 +64,9 @@ public class ValueIntegerExpressionChangeActionOperation implements ActionOperat @Override public void apply(RemoteContext context) {} + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } @@ -69,23 +75,27 @@ public class ValueIntegerExpressionChangeActionOperation implements ActionOperat @Override public void runAction( - RemoteContext context, CoreDocument document, Component component, float x, float y) { + @NonNull RemoteContext context, + @NonNull CoreDocument document, + Component component, + float x, + float y) { document.evaluateIntExpression(mValueExpressionId, (int) mTargetValueId, context); } - public static void apply(WireBuffer buffer, long valueId, long value) { + public static void apply(@NonNull WireBuffer buffer, long valueId, long value) { buffer.start(OP_CODE); buffer.writeLong(valueId); buffer.writeLong(value); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { long valueId = buffer.readLong(); long value = buffer.readLong(); operations.add(new ValueIntegerExpressionChangeActionOperation(valueId, value)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", OP_CODE, "ValueIntegerExpressionChangeActionOperation") .description( "ValueIntegerExpressionChange action. " diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java index a64a492a8cc1..26d7244eee8c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java @@ -17,6 +17,9 @@ package com.android.internal.widget.remotecompose.core.operations.layout.modifie import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; +import android.annotation.Nullable; + import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; @@ -41,6 +44,7 @@ public class ValueStringChangeActionOperation implements ActionOperation { mValueId = value; } + @NonNull @Override public String toString() { return "ValueChangeActionOperation(" + mTargetValueId + ")"; @@ -50,20 +54,22 @@ public class ValueStringChangeActionOperation implements ActionOperation { return mTargetValueId; } + @NonNull public String serializedName() { return "VALUE_CHANGE"; } @Override - public void serializeToString(int indent, StringSerializer serializer) { + public void serializeToString(int indent, @NonNull StringSerializer serializer) { serializer.append(indent, serializedName() + " = " + mTargetValueId + " -> " + mValueId); } @Override public void apply(RemoteContext context) {} + @NonNull @Override - public String deepToString(String indent) { + public String deepToString(@Nullable String indent) { return (indent != null ? indent : "") + toString(); } @@ -72,23 +78,27 @@ public class ValueStringChangeActionOperation implements ActionOperation { @Override public void runAction( - RemoteContext context, CoreDocument document, Component component, float x, float y) { + @NonNull RemoteContext context, + CoreDocument document, + Component component, + float x, + float y) { context.overrideText(mTargetValueId, mValueId); } - public static void apply(WireBuffer buffer, int valueId, int value) { + public static void apply(@NonNull WireBuffer buffer, int valueId, int value) { buffer.start(OP_CODE); buffer.writeInt(valueId); buffer.writeInt(value); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int valueId = buffer.readInt(); int value = buffer.readInt(); operations.add(new ValueStringChangeActionOperation(valueId, value)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Layout Operations", OP_CODE, "ValueStringChangeActionOperation") .description( "ValueStrin gChange action. " diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java index 62403b3e5e7e..e2f899ce2b46 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java @@ -18,6 +18,8 @@ package com.android.internal.widget.remotecompose.core.operations.layout.modifie import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.WireBuffer; @@ -30,6 +32,7 @@ public class WidthModifierOperation extends DimensionModifierOperation { private static final int OP_CODE = Operations.MODIFIER_WIDTH; public static final String CLASS_NAME = "WidthModifierOperation"; + @NonNull public static String name() { return CLASS_NAME; } @@ -38,13 +41,13 @@ public class WidthModifierOperation extends DimensionModifierOperation { return OP_CODE; } - public static void apply(WireBuffer buffer, int type, float value) { + public static void apply(@NonNull WireBuffer buffer, int type, float value) { buffer.start(OP_CODE); buffer.writeInt(type); buffer.writeFloat(value); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { Type type = Type.fromInt(buffer.readInt()); float value = buffer.readFloat(); Operation op = new WidthModifierOperation(type, value); @@ -56,7 +59,7 @@ public class WidthModifierOperation extends DimensionModifierOperation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mType.ordinal(), mValue); } @@ -68,17 +71,19 @@ public class WidthModifierOperation extends DimensionModifierOperation { super(value); } + @NonNull @Override public String toString() { - return "Width(" + mValue + ")"; + return "Width(" + mType + ", " + mValue + ")"; } + @NonNull @Override public String serializedName() { return "WIDTH"; } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) .description("define the animation") .field(INT, "type", "") diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java new file mode 100644 index 000000000000..aa20e0388d31 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java @@ -0,0 +1,105 @@ +/* + * 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.internal.widget.remotecompose.core.operations.layout.modifiers; + +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.operations.Utils; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; + +import java.util.List; + +/** Represents a ZIndex modifier, allowing to change the z-index of a component. */ +public class ZIndexModifierOperation extends DecoratorModifierOperation { + private static final int OP_CODE = Operations.MODIFIER_ZINDEX; + public static final String CLASS_NAME = "ZIndexModifierOperation"; + float mValue; + float mCurrentValue; + + public ZIndexModifierOperation(float value) { + this.mValue = value; + } + + public float getValue() { + return mCurrentValue; + } + + public void setmValue(float value) { + this.mValue = value; + } + + @Override + public void write(WireBuffer buffer) { + apply(buffer, mValue); + } + + // @Override + public void serializeToString(int indent, StringSerializer serializer) { + serializer.append(indent, "ZINDEX = [" + mValue + "]"); + } + + @Override + public String deepToString(String indent) { + return (indent != null ? indent : "") + toString(); + } + + @Override + public void paint(PaintContext context) { + mCurrentValue = mValue; + if (Utils.isVariable(mValue)) { + mCurrentValue = + context.getContext().mRemoteComposeState.getFloat(Utils.idFromNan(mValue)); + } + } + + @Override + public String toString() { + return "ZIndexModifierOperation(" + mValue + ")"; + } + + public static String name() { + return CLASS_NAME; + } + + public static int id() { + return OP_CODE; + } + + public static void apply(WireBuffer buffer, float value) { + buffer.start(OP_CODE); + buffer.writeFloat(value); + } + + public static void read(WireBuffer buffer, List<Operation> operations) { + float value = buffer.readFloat(); + operations.add(new ZIndexModifierOperation(value)); + } + + public static void documentation(DocumentationBuilder doc) { + doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) + .description("define the Z-Index Modifier") + .field(FLOAT, "value", ""); + } + + @Override + public void layout(RemoteContext context, float width, float height) {} +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java index 4849b12c65b1..d8e49b0a9ccd 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout.utils; +import android.annotation.NonNull; +import android.annotation.Nullable; + import java.util.ArrayList; /** Internal utility debug class */ @@ -23,12 +26,12 @@ public class DebugLog { public static final boolean DEBUG_LAYOUT_ON = false; public static class Node { - public Node parent; + @Nullable public Node parent; public String name; public String endString; - public ArrayList<Node> list = new ArrayList<>(); + @NonNull public ArrayList<Node> list = new ArrayList<>(); - public Node(Node parent, String name) { + public Node(@Nullable Node parent, String name) { this.parent = parent; this.name = name; this.endString = name + " DONE"; @@ -48,21 +51,21 @@ public class DebugLog { } } - public static Node node = new Node(null, "Root"); - public static Node currentNode = node; + @NonNull public static Node node = new Node(null, "Root"); + @NonNull public static Node currentNode = node; public static void clear() { node = new Node(null, "Root"); currentNode = node; } - public static void s(StringValueSupplier valueSupplier) { + public static void s(@NonNull StringValueSupplier valueSupplier) { if (DEBUG_LAYOUT_ON) { currentNode = new Node(currentNode, valueSupplier.getString()); } } - public static void log(StringValueSupplier valueSupplier) { + public static void log(@NonNull StringValueSupplier valueSupplier) { if (DEBUG_LAYOUT_ON) { new LogNode(currentNode, valueSupplier.getString()); } @@ -78,7 +81,7 @@ public class DebugLog { } } - public static void e(StringValueSupplier valueSupplier) { + public static void e(@NonNull StringValueSupplier valueSupplier) { if (DEBUG_LAYOUT_ON) { currentNode.endString = valueSupplier.getString(); if (currentNode.parent != null) { @@ -89,7 +92,7 @@ public class DebugLog { } } - public static void printNode(int indent, Node node, StringBuilder builder) { + public static void printNode(int indent, @NonNull Node node, @NonNull StringBuilder builder) { if (DEBUG_LAYOUT_ON) { StringBuilder indentationBuilder = new StringBuilder(); for (int i = 0; i < indent; i++) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java index 9a3cd54c3f85..a808cf0e17b3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.paint; +import android.annotation.NonNull; + /** Provides a Builder pattern for a PaintBundle */ class Painter { PaintBundle mPaint; @@ -24,16 +26,19 @@ class Painter { return mPaint; } + @NonNull public Painter setAntiAlias(boolean aa) { mPaint.setAntiAlias(aa); return this; } + @NonNull public Painter setColor(int color) { mPaint.setColor(color); return this; } + @NonNull public Painter setColorId(int colorId) { mPaint.setColorId(colorId); return this; @@ -44,6 +49,7 @@ class Painter { * * @param join set the paint's Join, used whenever the paint's style is Stroke or StrokeAndFill. */ + @NonNull public Painter setStrokeJoin(int join) { mPaint.setStrokeJoin(join); return this; @@ -56,6 +62,7 @@ class Painter { * @param width set the paint's stroke width, used whenever the paint's style is Stroke or * StrokeAndFill. */ + @NonNull public Painter setStrokeWidth(float width) { mPaint.setStrokeWidth(width); return this; @@ -67,6 +74,7 @@ class Painter { * * @param style The new style to set in the paint */ + @NonNull public Painter setStyle(int style) { mPaint.setStyle(style); return this; @@ -78,6 +86,7 @@ class Painter { * @param cap set the paint's line cap style, used whenever the paint's style is Stroke or * StrokeAndFill. */ + @NonNull public Painter setStrokeCap(int cap) { mPaint.setStrokeCap(cap); return this; @@ -90,6 +99,7 @@ class Painter { * @param miter set the miter limit on the paint, used whenever the paint's style is Stroke or * StrokeAndFill. */ + @NonNull public Painter setStrokeMiter(float miter) { mPaint.setStrokeMiter(miter); return this; @@ -101,6 +111,7 @@ class Painter { * * @param alpha set the alpha component [0..1.0] of the paint's color. */ + @NonNull public Painter setAlpha(float alpha) { mPaint.setAlpha((alpha > 2) ? alpha / 255f : alpha); return this; @@ -112,6 +123,7 @@ class Painter { * @param color The ARGB source color used with the specified Porter-Duff mode * @param mode The porter-duff mode that is applied */ + @NonNull public Painter setPorterDuffColorFilter(int color, int mode) { mPaint.setColorFilter(color, mode); return this; @@ -130,6 +142,7 @@ class Painter { * line. * @param tileMode The Shader tiling mode */ + @NonNull public Painter setLinearGradient( float startX, float startY, @@ -155,6 +168,7 @@ class Painter { * circle. * @param tileMode The Shader tiling mode */ + @NonNull public Painter setRadialGradient( float centerX, float centerY, @@ -178,6 +192,7 @@ class Painter { * may produce unexpected results. If positions is NULL, then the colors are automatically * spaced evenly. */ + @NonNull public Painter setSweepGradient(float centerX, float centerY, int[] colors, float[] positions) { mPaint.setSweepGradient(colors, 0, positions, centerX, centerY); return this; @@ -188,6 +203,7 @@ class Painter { * * @param size set the paint's text size in pixel units. */ + @NonNull public Painter setTextSize(float size) { mPaint.setTextSize(size); return this; @@ -215,16 +231,19 @@ class Painter { * @param weight The desired weight to be drawn. * @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false} */ + @NonNull public Painter setTypeface(int fontType, int weight, boolean italic) { mPaint.setTextStyle(fontType, weight, italic); return this; } + @NonNull public Painter setFilterBitmap(boolean filter) { mPaint.setFilterBitmap(filter); return this; } + @NonNull public Painter setShader(int id) { mPaint.setShader(id); return this; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java index 1d673c4c5ab6..b25f4cd3c530 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java @@ -15,11 +15,12 @@ */ package com.android.internal.widget.remotecompose.core.operations.utilities; -/** - * high performance floating point expression evaluator used in animation - */ +import android.annotation.NonNull; +import android.annotation.Nullable; + +/** high performance floating point expression evaluator used in animation */ public class AnimatedFloatExpression { - static IntMap<String> sNames = new IntMap<>(); + @NonNull static IntMap<String> sNames = new IntMap<>(); public static final int OFFSET = 0x310_000; public static final float ADD = asNan(OFFSET + 1); public static final float SUB = asNan(OFFSET + 2); @@ -74,7 +75,7 @@ public class AnimatedFloatExpression { private static final float FP_TO_DEG = 0.017453292f; // 180/PI float[] mStack; - float[] mLocalStack = new float[128]; + @NonNull float[] mLocalStack = new float[128]; float[] mVar; CollectionsAccess mCollectionsAccess; @@ -201,7 +202,7 @@ public class AnimatedFloatExpression { * @param var * @return */ - public float eval(float[] exp, int len, float... var) { + public float eval(@NonNull float[] exp, int len, float... var) { System.arraycopy(exp, 0, mLocalStack, 0, len); mStack = mLocalStack; mVar = var; @@ -224,7 +225,7 @@ public class AnimatedFloatExpression { * @param var * @return */ - public float evalDB(float[] exp, float... var) { + public float evalDB(@NonNull float[] exp, float... var) { mStack = exp; mVar = var; int sp = -1; @@ -240,195 +241,281 @@ public class AnimatedFloatExpression { return mStack[sp]; } - Op[] mOps = { + @NonNull Op[] mOps; + + { + Op mADD = + (sp) -> { // ADD + mStack[sp - 1] = mStack[sp - 1] + mStack[sp]; + return sp - 1; + }; + Op mSUB = + (sp) -> { // SUB + mStack[sp - 1] = mStack[sp - 1] - mStack[sp]; + return sp - 1; + }; + Op mMUL = + (sp) -> { // MUL + mStack[sp - 1] = mStack[sp - 1] * mStack[sp]; + return sp - 1; + }; + Op mDIV = + (sp) -> { // DIV + mStack[sp - 1] = mStack[sp - 1] / mStack[sp]; + return sp - 1; + }; + Op mMOD = + (sp) -> { // MOD + mStack[sp - 1] = mStack[sp - 1] % mStack[sp]; + return sp - 1; + }; + Op mMIN = + (sp) -> { // MIN + mStack[sp - 1] = (float) Math.min(mStack[sp - 1], mStack[sp]); + return sp - 1; + }; + Op mMAX = + (sp) -> { // MAX + mStack[sp - 1] = (float) Math.max(mStack[sp - 1], mStack[sp]); + return sp - 1; + }; + Op mPOW = + (sp) -> { // POW + mStack[sp - 1] = (float) Math.pow(mStack[sp - 1], mStack[sp]); + return sp - 1; + }; + Op mSQRT = + (sp) -> { // SQRT + mStack[sp] = (float) Math.sqrt(mStack[sp]); + return sp; + }; + Op mABS = + (sp) -> { // ABS + mStack[sp] = (float) Math.abs(mStack[sp]); + return sp; + }; + Op mSIGN = + (sp) -> { // SIGN + mStack[sp] = (float) Math.signum(mStack[sp]); + return sp; + }; + Op mCOPY_SIGN = + (sp) -> { // copySign + mStack[sp - 1] = (float) Math.copySign(mStack[sp - 1], mStack[sp]); + return sp - 1; + }; + Op mEXP = + (sp) -> { // EXP + mStack[sp] = (float) Math.exp(mStack[sp]); + return sp; + }; + Op mFLOOR = + (sp) -> { // FLOOR + mStack[sp] = (float) Math.floor(mStack[sp]); + return sp; + }; + Op mLOG = + (sp) -> { // LOG + mStack[sp] = (float) Math.log10(mStack[sp]); + return sp; + }; + Op mLN = + (sp) -> { // LN + mStack[sp] = (float) Math.log(mStack[sp]); + return sp; + }; + Op mROUND = + (sp) -> { // ROUND + mStack[sp] = (float) Math.round(mStack[sp]); + return sp; + }; + Op mSIN = + (sp) -> { // SIN + mStack[sp] = (float) Math.sin(mStack[sp]); + return sp; + }; + Op mCOS = + (sp) -> { // COS + mStack[sp] = (float) Math.cos(mStack[sp]); + return sp; + }; + Op mTAN = + (sp) -> { // TAN + mStack[sp] = (float) Math.tan(mStack[sp]); + return sp; + }; + Op mASIN = + (sp) -> { // ASIN + mStack[sp] = (float) Math.asin(mStack[sp]); + return sp; + }; + Op mACOS = + (sp) -> { // ACOS + mStack[sp] = (float) Math.acos(mStack[sp]); + return sp; + }; + Op mATAN = + (sp) -> { // ATAN + mStack[sp] = (float) Math.atan(mStack[sp]); + return sp; + }; + Op mATAN2 = + (sp) -> { // ATAN2 + mStack[sp - 1] = (float) Math.atan2(mStack[sp - 1], mStack[sp]); + return sp - 1; + }; + Op mMAD = + (sp) -> { // MAD + mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2]; + return sp - 2; + }; + Op mTERNARY_CONDITIONAL = + (sp) -> { // TERNARY_CONDITIONAL + mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2]; + return sp - 2; + }; + Op mCLAMP = + (sp) -> { // CLAMP + mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]); + return sp - 2; + }; + Op mCBRT = + (sp) -> { // CBRT + mStack[sp] = (float) Math.pow(mStack[sp], 1 / 3.); + return sp; + }; + Op mDEG = + (sp) -> { // DEG + mStack[sp] = mStack[sp] * FP_TO_RAD; + return sp; + }; + Op mRAD = + (sp) -> { // RAD + mStack[sp] = mStack[sp] * FP_TO_DEG; + return sp; + }; + Op mCEIL = + (sp) -> { // CEIL + mStack[sp] = (float) Math.ceil(mStack[sp]); + return sp; + }; + Op mA_DEREF = + (sp) -> { // A_DEREF + int id = fromNaN(mStack[sp]); + mStack[sp - 1] = mCollectionsAccess.getFloatValue(id, (int) mStack[sp - 1]); + return sp - 1; + }; + Op mA_MAX = + (sp) -> { // A_MAX + int id = fromNaN(mStack[sp]); + float[] array = mCollectionsAccess.getFloats(id); + float max = array[0]; + for (int i = 1; i < array.length; i++) { + max = Math.max(max, array[i]); + } + mStack[sp] = max; + return sp; + }; + Op mA_MIN = + (sp) -> { // A_MIN + int id = fromNaN(mStack[sp]); + float[] array = mCollectionsAccess.getFloats(id); + float max = array[0]; + for (int i = 1; i < array.length; i++) { + max = Math.max(max, array[i]); + } + mStack[sp] = max; + return sp; + }; + Op mA_SUM = + (sp) -> { // A_SUM + int id = fromNaN(mStack[sp]); + float[] array = mCollectionsAccess.getFloats(id); + float sum = 0; + for (int i = 0; i < array.length; i++) { + sum += array[i]; + } + mStack[sp] = sum; + return sp; + }; + Op mA_AVG = + (sp) -> { // A_AVG + int id = fromNaN(mStack[sp]); + float[] array = mCollectionsAccess.getFloats(id); + float sum = 0; + for (int i = 0; i < array.length; i++) { + sum += array[i]; + } + mStack[sp] = sum / array.length; + return sp; + }; + Op mA_LEN = + (sp) -> { // A_LEN + int id = fromNaN(mStack[sp]); + mStack[sp] = mCollectionsAccess.getListLength(id); + return sp; + }; + Op mFIRST_VAR = + (sp) -> { // FIRST_VAR + mStack[sp] = mVar[0]; + return sp; + }; + Op mSECOND_VAR = + (sp) -> { // SECOND_VAR + mStack[sp] = mVar[1]; + return sp; + }; + Op mTHIRD_VAR = + (sp) -> { // THIRD_VAR + mStack[sp] = mVar[2]; + return sp; + }; + + Op[] ops = { null, - (sp) -> { // ADD - mStack[sp - 1] = mStack[sp - 1] + mStack[sp]; - return sp - 1; - }, - (sp) -> { // SUB - mStack[sp - 1] = mStack[sp - 1] - mStack[sp]; - return sp - 1; - }, - (sp) -> { // MUL - mStack[sp - 1] = mStack[sp - 1] * mStack[sp]; - return sp - 1; - }, - (sp) -> { // DIV - mStack[sp - 1] = mStack[sp - 1] / mStack[sp]; - return sp - 1; - }, - (sp) -> { // MOD - mStack[sp - 1] = mStack[sp - 1] % mStack[sp]; - return sp - 1; - }, - (sp) -> { // MIN - mStack[sp - 1] = (float) Math.min(mStack[sp - 1], mStack[sp]); - return sp - 1; - }, - (sp) -> { // MAX - mStack[sp - 1] = (float) Math.max(mStack[sp - 1], mStack[sp]); - return sp - 1; - }, - (sp) -> { // POW - mStack[sp - 1] = (float) Math.pow(mStack[sp - 1], mStack[sp]); - return sp - 1; - }, - (sp) -> { // SQRT - mStack[sp] = (float) Math.sqrt(mStack[sp]); - return sp; - }, - (sp) -> { // ABS - mStack[sp] = (float) Math.abs(mStack[sp]); - return sp; - }, - (sp) -> { // SIGN - mStack[sp] = (float) Math.signum(mStack[sp]); - return sp; - }, - (sp) -> { // copySign - mStack[sp - 1] = (float) Math.copySign(mStack[sp - 1], mStack[sp]); - return sp - 1; - }, - (sp) -> { // EXP - mStack[sp] = (float) Math.exp(mStack[sp]); - return sp; - }, - (sp) -> { // FLOOR - mStack[sp] = (float) Math.floor(mStack[sp]); - return sp; - }, - (sp) -> { // LOG - mStack[sp] = (float) Math.log10(mStack[sp]); - return sp; - }, - (sp) -> { // LN - mStack[sp] = (float) Math.log(mStack[sp]); - return sp; - }, - (sp) -> { // ROUND - mStack[sp] = (float) Math.round(mStack[sp]); - return sp; - }, - (sp) -> { // SIN - mStack[sp] = (float) Math.sin(mStack[sp]); - return sp; - }, - (sp) -> { // COS - mStack[sp] = (float) Math.cos(mStack[sp]); - return sp; - }, - (sp) -> { // TAN - mStack[sp] = (float) Math.tan(mStack[sp]); - return sp; - }, - (sp) -> { // ASIN - mStack[sp] = (float) Math.asin(mStack[sp]); - return sp; - }, - (sp) -> { // ACOS - mStack[sp] = (float) Math.acos(mStack[sp]); - return sp; - }, - (sp) -> { // ATAN - mStack[sp] = (float) Math.atan(mStack[sp]); - return sp; - }, - (sp) -> { // ATAN2 - mStack[sp - 1] = (float) Math.atan2(mStack[sp - 1], mStack[sp]); - return sp - 1; - }, - (sp) -> { // MAD - mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2]; - return sp - 2; - }, - (sp) -> { // Ternary conditional - mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2]; - return sp - 2; - }, - (sp) -> { // CLAMP(min,max, val) - mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]); - return sp - 2; - }, - (sp) -> { // CBRT cuberoot - mStack[sp] = (float) Math.pow(mStack[sp], 1 / 3.); - return sp; - }, - (sp) -> { // DEG - mStack[sp] = mStack[sp] * FP_TO_RAD; - return sp; - }, - (sp) -> { // RAD - mStack[sp] = mStack[sp] * FP_TO_DEG; - return sp; - }, - (sp) -> { // CEIL - mStack[sp] = (float) Math.ceil(mStack[sp]); - return sp; - }, - (sp) -> { // A_DEREF - int id = fromNaN(mStack[sp]); - mStack[sp] = mCollectionsAccess.getFloatValue(id, (int) mStack[sp - 1]); - return sp - 1; - }, - (sp) -> { // A_MAX - int id = fromNaN(mStack[sp]); - float[] array = mCollectionsAccess.getFloats(id); - float max = array[0]; - for (int i = 1; i < array.length; i++) { - max = Math.max(max, array[i]); - } - mStack[sp] = max; - return sp; - }, - (sp) -> { // A_MIN - int id = fromNaN(mStack[sp]); - float[] array = mCollectionsAccess.getFloats(id); - float max = array[0]; - for (int i = 1; i < array.length; i++) { - max = Math.max(max, array[i]); - } - mStack[sp] = max; - return sp; - }, - (sp) -> { // A_SUM - int id = fromNaN(mStack[sp]); - float[] array = mCollectionsAccess.getFloats(id); - float sum = 0; - for (int i = 0; i < array.length; i++) { - sum += array[i]; - } - mStack[sp] = sum; - return sp; - }, - (sp) -> { // A_AVG - int id = fromNaN(mStack[sp]); - float[] array = mCollectionsAccess.getFloats(id); - float sum = 0; - for (int i = 0; i < array.length; i++) { - sum += array[i]; - } - mStack[sp] = sum / array.length; - return sp; - }, - (sp) -> { // A_LEN - int id = fromNaN(mStack[sp]); - mStack[sp] = mCollectionsAccess.getListLength(id); - return sp; - }, - (sp) -> { // first var = - mStack[sp] = mVar[0]; - return sp; - }, - (sp) -> { // second var y? - mStack[sp] = mVar[1]; - return sp; - }, - (sp) -> { // 3rd var z? - mStack[sp] = mVar[2]; - return sp; - }, - }; + mADD, + mSUB, + mMUL, + mDIV, + mMOD, + mMIN, + mMAX, + mPOW, + mSQRT, + mABS, + mSIGN, + mCOPY_SIGN, + mEXP, + mFLOOR, + mLOG, + mLN, + mROUND, + mSIN, + mCOS, + mTAN, + mASIN, + mACOS, + mATAN, + mATAN2, + mMAD, + mTERNARY_CONDITIONAL, + mCLAMP, + mCBRT, + mDEG, + mRAD, + mCEIL, + mA_DEREF, + mA_MAX, + mA_MIN, + mA_SUM, + mA_AVG, + mA_LEN, + mFIRST_VAR, + mSECOND_VAR, + mTHIRD_VAR, + }; + mOps = ops; + } static { int k = 0; @@ -483,6 +570,7 @@ public class AnimatedFloatExpression { * @param f * @return */ + @Nullable public static String toMathName(float f) { int id = fromNaN(f) - OFFSET; return sNames.get(id); @@ -495,7 +583,8 @@ public class AnimatedFloatExpression { * @param labels * @return */ - public static String toString(float[] exp, String[] labels) { + @NonNull + public static String toString(@NonNull float[] exp, @Nullable String[] labels) { StringBuilder s = new StringBuilder(); for (int i = 0; i < exp.length; i++) { float v = exp[i]; @@ -525,7 +614,7 @@ public class AnimatedFloatExpression { return s.toString(); } - static String toString(float[] exp, int sp) { + static String toString(@NonNull float[] exp, int sp) { // String[] str = new String[exp.length]; if (Float.isNaN(exp[sp])) { int id = fromNaN(exp[sp]) - OFFSET; @@ -575,42 +664,42 @@ public class AnimatedFloatExpression { } static final int[] NO_OF_OPS = { - -1, // no op - 2, - 2, - 2, - 2, - 2, // + - * / % - 2, - 2, - 2, // min max, power - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, // sqrt,abs,CopySign,exp,floor,log,ln - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 2, // round,sin,cos,tan,asin,acos,atan,atan2 - 3, - 3, - 3, - 1, - 1, - 1, - 1, - 0, - 0, - 0 // mad, ?:, - // a[0],a[1],a[2] + -1, // no op + 2, + 2, + 2, + 2, + 2, // + - * / % + 2, + 2, + 2, // min max, power + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, // sqrt,abs,CopySign,exp,floor,log,ln + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, // round,sin,cos,tan,asin,acos,atan,atan2 + 3, + 3, + 3, + 1, + 1, + 1, + 1, + 0, + 0, + 0 // mad, ?:, + // a[0],a[1],a[2] }; /** diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java index 00c87c1f9c80..e74b3350f427 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.utilities; +import android.annotation.NonNull; + /** Implement the scaling logic for Compose Image or ImageView */ public class ImageScaling { @@ -97,6 +99,7 @@ public class ImageScaling { adjustDrawToType(); } + @NonNull static String str(float v) { String s = " " + (int) v; return s.substring(s.length() - 3); @@ -210,6 +213,7 @@ public class ImageScaling { } } + @NonNull public static String typeToString(int type) { String[] typeString = { "none", diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java index 84e78431790a..749c0fe0dcc3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.utilities; +import android.annotation.Nullable; + import java.util.ArrayList; import java.util.Arrays; @@ -42,6 +44,7 @@ public class IntMap<T> { mSize = 0; } + @Nullable public T put(int key, T value) { if (key == NOT_PRESENT) throw new IllegalArgumentException("Key cannot be NOT_PRESENT"); if (mSize > mKeys.length * LOAD_FACTOR) { @@ -50,6 +53,7 @@ public class IntMap<T> { return insert(key, value); } + @Nullable public T get(int key) { int index = findKey(key); if (index == -1) { @@ -61,6 +65,7 @@ public class IntMap<T> { return mSize; } + @Nullable private T insert(int key, T value) { int index = hash(key) % mKeys.length; while (mKeys[index] != NOT_PRESENT && mKeys[index] != key) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java index baa144d6b28d..8905431d14d7 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core.operations.utilities; +import android.annotation.NonNull; +import android.annotation.Nullable; + /** * High performance Integer expression evaluator * @@ -22,7 +25,7 @@ package com.android.internal.widget.remotecompose.core.operations.utilities; * 0) */ public class IntegerExpressionEvaluator { - static IntMap<String> sNames = new IntMap<>(); + @NonNull static IntMap<String> sNames = new IntMap<>(); public static final int OFFSET = 0x10000; // add, sub, mul,div,mod,min,max, shl, shr, ushr, OR, AND , XOR, COPY_SIGN public static final int I_ADD = OFFSET + 1; @@ -57,7 +60,7 @@ public class IntegerExpressionEvaluator { public static final int I_VAR2 = OFFSET + 25; int[] mStack; - int[] mLocalStack = new int[128]; + @NonNull int[] mLocalStack = new int[128]; int[] mVar; interface Op { @@ -68,8 +71,8 @@ public class IntegerExpressionEvaluator { * Evaluate an integer expression * * @param mask bits that are operators - * @param exp rpn sequence of values and operators - * @param var variables if the expression is a function + * @param exp rpn sequence of values and operators + * @param var variables if the expression is a function * @return return the results of evaluating the expression */ public int eval(int mask, int[] exp, int... var) { @@ -91,12 +94,12 @@ public class IntegerExpressionEvaluator { * Evaluate a integer expression * * @param mask bits that are operators - * @param exp rpn sequence of values and operators - * @param len the number of values in the expression - * @param var variables if the expression is a function + * @param exp rpn sequence of values and operators + * @param len the number of values in the expression + * @param var variables if the expression is a function * @return return the results of evaluating the expression */ - public int eval(int mask, int[] exp, int len, int... var) { + public int eval(int mask, @NonNull int[] exp, int len, int... var) { System.arraycopy(exp, 0, mLocalStack, 0, len); mStack = mLocalStack; mVar = var; @@ -116,11 +119,11 @@ public class IntegerExpressionEvaluator { * Evaluate a int expression * * @param opMask bits that are operators - * @param exp rpn sequence of values and operators - * @param var variables if the expression is a function + * @param exp rpn sequence of values and operators + * @param var variables if the expression is a function * @return return the results of evaluating the expression */ - public int evalDB(int opMask, int[] exp, int... var) { + public int evalDB(int opMask, @NonNull int[] exp, int... var) { mStack = exp; mVar = var; int sp = -1; @@ -137,113 +140,172 @@ public class IntegerExpressionEvaluator { return mStack[sp]; } - Op[] mOps = { + @NonNull Op[] mOps; + + { + Op mADD = + (sp) -> { // ADD + mStack[sp - 1] = mStack[sp - 1] + mStack[sp]; + return sp - 1; + }; + Op mSUB = + (sp) -> { // SUB + mStack[sp - 1] = mStack[sp - 1] - mStack[sp]; + return sp - 1; + }; + Op mMUL = + (sp) -> { // MUL + mStack[sp - 1] = mStack[sp - 1] * mStack[sp]; + return sp - 1; + }; + Op mDIV = + (sp) -> { // DIV + mStack[sp - 1] = mStack[sp - 1] / mStack[sp]; + return sp - 1; + }; + Op mMOD = + (sp) -> { // MOD + mStack[sp - 1] = mStack[sp - 1] % mStack[sp]; + return sp - 1; + }; + Op mSHL = + (sp) -> { // SHL + mStack[sp - 1] = mStack[sp - 1] << mStack[sp]; + return sp - 1; + }; + Op mSHR = + (sp) -> { // SHR + mStack[sp - 1] = mStack[sp - 1] >> mStack[sp]; + return sp - 1; + }; + Op mUSHR = + (sp) -> { // USHR + mStack[sp - 1] = mStack[sp - 1] >>> mStack[sp]; + return sp - 1; + }; + Op mOR = + (sp) -> { // OR + mStack[sp - 1] = mStack[sp - 1] | mStack[sp]; + return sp - 1; + }; + Op mAND = + (sp) -> { // AND + mStack[sp - 1] = mStack[sp - 1] & mStack[sp]; + return sp - 1; + }; + Op mXOR = + (sp) -> { // XOR + mStack[sp - 1] = mStack[sp - 1] ^ mStack[sp]; + return sp - 1; + }; + Op mCOPY_SIGN = + (sp) -> { // COPY_SIGN + mStack[sp - 1] = (mStack[sp - 1] ^ (mStack[sp] >> 31)) - (mStack[sp] >> 31); + return sp - 1; + }; + Op mMIN = + (sp) -> { // MIN + mStack[sp - 1] = Math.min(mStack[sp - 1], mStack[sp]); + return sp - 1; + }; + Op mMAX = + (sp) -> { // MAX + mStack[sp - 1] = Math.max(mStack[sp - 1], mStack[sp]); + return sp - 1; + }; + Op mNEG = + (sp) -> { // NEG + mStack[sp] = -mStack[sp]; + return sp; + }; + Op mABS = + (sp) -> { // ABS + mStack[sp] = Math.abs(mStack[sp]); + return sp; + }; + Op mINCR = + (sp) -> { // INCR + mStack[sp] = mStack[sp] + 1; + return sp; + }; + Op mDECR = + (sp) -> { // DECR + mStack[sp] = mStack[sp] - 1; + return sp; + }; + Op mNOT = + (sp) -> { // NOT + mStack[sp] = ~mStack[sp]; + return sp; + }; + Op mSIGN = + (sp) -> { // SIGN + mStack[sp] = (mStack[sp] >> 31) | (-mStack[sp] >>> 31); + return sp; + }; + Op mCLAMP = + (sp) -> { // CLAMP + mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]); + return sp - 2; + }; + Op mTERNARY_CONDITIONAL = + (sp) -> { // TERNARY_CONDITIONAL + mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2]; + return sp - 2; + }; + Op mMAD = + (sp) -> { // MAD + mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2]; + return sp - 2; + }; + Op mFIRST_VAR = + (sp) -> { // FIRST_VAR + mStack[sp] = mVar[0]; + return sp; + }; + Op mSECOND_VAR = + (sp) -> { // SECOND_VAR + mStack[sp] = mVar[1]; + return sp; + }; + Op mTHIRD_VAR = + (sp) -> { // THIRD_VAR + mStack[sp] = mVar[2]; + return sp; + }; + + Op[] ops = { null, - (sp) -> { // ADD - mStack[sp - 1] = mStack[sp - 1] + mStack[sp]; - return sp - 1; - }, - (sp) -> { // SUB - mStack[sp - 1] = mStack[sp - 1] - mStack[sp]; - return sp - 1; - }, - (sp) -> { // MUL - mStack[sp - 1] = mStack[sp - 1] * mStack[sp]; - return sp - 1; - }, - (sp) -> { // DIV - mStack[sp - 1] = mStack[sp - 1] / mStack[sp]; - return sp - 1; - }, - (sp) -> { // MOD - mStack[sp - 1] = mStack[sp - 1] % mStack[sp]; - return sp - 1; - }, - (sp) -> { // SHL shift left - mStack[sp - 1] = mStack[sp - 1] << mStack[sp]; - return sp - 1; - }, - (sp) -> { // SHR shift right - mStack[sp - 1] = mStack[sp - 1] >> mStack[sp]; - return sp - 1; - }, - (sp) -> { // USHR unsigned shift right - mStack[sp - 1] = mStack[sp - 1] >>> mStack[sp]; - return sp - 1; - }, - (sp) -> { // OR operator - mStack[sp - 1] = mStack[sp - 1] | mStack[sp]; - return sp - 1; - }, - (sp) -> { // AND operator - mStack[sp - 1] = mStack[sp - 1] & mStack[sp]; - return sp - 1; - }, - (sp) -> { // XOR xor operator - mStack[sp - 1] = mStack[sp - 1] ^ mStack[sp]; - return sp - 1; - }, - (sp) -> { // COPY_SIGN copy the sing of (using bit magic) - mStack[sp - 1] = (mStack[sp - 1] ^ (mStack[sp] >> 31)) - (mStack[sp] >> 31); - return sp - 1; - }, - (sp) -> { // MIN - mStack[sp - 1] = Math.min(mStack[sp - 1], mStack[sp]); - return sp - 1; - }, - (sp) -> { // MAX - mStack[sp - 1] = Math.max(mStack[sp - 1], mStack[sp]); - return sp - 1; - }, - (sp) -> { // NEG - mStack[sp] = -mStack[sp]; - return sp; - }, - (sp) -> { // ABS - mStack[sp] = Math.abs(mStack[sp]); - return sp; - }, - (sp) -> { // INCR increment - mStack[sp] = mStack[sp] + 1; - return sp; - }, - (sp) -> { // DECR decrement - mStack[sp] = mStack[sp] - 1; - return sp; - }, - (sp) -> { // NOT Bit invert - mStack[sp] = ~mStack[sp]; - return sp; - }, - (sp) -> { // SIGN x<0 = -1,x==0 = 0 , x>0 = 1 - mStack[sp] = (mStack[sp] >> 31) | (-mStack[sp] >>> 31); - return sp; - }, - (sp) -> { // CLAMP(min,max, val) - mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]); - return sp - 2; - }, - (sp) -> { // Ternary conditional - mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2]; - return sp - 2; - }, - (sp) -> { // MAD - mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2]; - return sp - 2; - }, - (sp) -> { // first var = - mStack[sp] = mVar[0]; - return sp; - }, - (sp) -> { // second var y? - mStack[sp] = mVar[1]; - return sp; - }, - (sp) -> { // 3rd var z? - mStack[sp] = mVar[2]; - return sp; - }, - }; + mADD, + mSUB, + mMUL, + mDIV, + mMOD, + mSHL, + mSHR, + mUSHR, + mOR, + mAND, + mXOR, + mCOPY_SIGN, + mMIN, + mMAX, + mNEG, + mABS, + mINCR, + mDECR, + mNOT, + mSIGN, + mCLAMP, + mTERNARY_CONDITIONAL, + mMAD, + mFIRST_VAR, + mSECOND_VAR, + mTHIRD_VAR, + }; + + mOps = ops; + } static { int k = 0; @@ -283,6 +345,7 @@ public class IntegerExpressionEvaluator { * @param f the numerical value of the function + offset * @return the math name of the function */ + @Nullable public static String toMathName(int f) { int id = f - OFFSET; return sNames.get(id); @@ -292,11 +355,12 @@ public class IntegerExpressionEvaluator { * Convert an expression encoded as an array of ints int to a string * * @param opMask bits that are operators - * @param exp rpn sequence of values and operators + * @param exp rpn sequence of values and operators * @param labels String that represent the variable names * @return */ - public static String toString(int opMask, int[] exp, String[] labels) { + @NonNull + public static String toString(int opMask, @NonNull int[] exp, String[] labels) { StringBuilder s = new StringBuilder(); for (int i = 0; i < exp.length; i++) { int v = exp[i]; @@ -324,10 +388,11 @@ public class IntegerExpressionEvaluator { * Convert an expression encoded as an array of ints int ot a string * * @param opMask bit mask of operators vs commands - * @param exp rpn sequence of values and operators + * @param exp rpn sequence of values and operators * @return string representation of the expression */ - public static String toString(int opMask, int[] exp) { + @NonNull + public static String toString(int opMask, @NonNull int[] exp) { StringBuilder s = new StringBuilder(); s.append(Integer.toBinaryString(opMask)); s.append(" : "); @@ -355,13 +420,15 @@ public class IntegerExpressionEvaluator { * This creates an infix string expression * * @param opMask The bits that are operators - * @param exp the array of expressions + * @param exp the array of expressions * @return infix string */ - public static String toStringInfix(int opMask, int[] exp) { + @NonNull + public static String toStringInfix(int opMask, @NonNull int[] exp) { return toString(opMask, exp, exp.length - 1); } + @NonNull static String toString(int mask, int[] exp, int sp) { if (((1 << sp) & mask) != 0) { int id = exp[sp] - OFFSET; @@ -412,34 +479,34 @@ public class IntegerExpressionEvaluator { } static final int[] NO_OF_OPS = { - -1, // no op - 2, - 2, - 2, - 2, - 2, // + - * / % - 2, - 2, - 2, - 2, - 2, - 2, - 2, - 2, - 2, // <<, >> , >>> , | , &, ^, min max - 1, - 1, - 1, - 1, - 1, - 1, // neg, abs, ++, -- , not , sign - 3, - 3, - 3, // clamp, ifElse, mad, - 0, - 0, - 0 // mad, ?:, - // a[0],a[1],a[2] + -1, // no op + 2, + 2, + 2, + 2, + 2, // + - * / % + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, // <<, >> , >>> , | , &, ^, min max + 1, + 1, + 1, + 1, + 1, + 1, // neg, abs, ++, -- , not , sign + 3, + 3, + 3, // clamp, ifElse, mad, + 0, + 0, + 0 // mad, ?:, + // a[0],a[1],a[2] }; /** @@ -456,7 +523,7 @@ public class IntegerExpressionEvaluator { * is it an id or operation * * @param opMask the bits that mark elements as an operation - * @param i the bit to check + * @param i the bit to check * @return true if the bit is 1 */ public static boolean isOperation(int opMask, int i) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java index ab7576e12aa6..92127c12f401 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java @@ -15,9 +15,14 @@ */ package com.android.internal.widget.remotecompose.core.operations.utilities; +import android.annotation.NonNull; +import android.annotation.Nullable; + /** Utility serializer maintaining an indent buffer */ public class StringSerializer { - StringBuffer mBuffer = new StringBuffer(); + @NonNull StringBuffer mBuffer = new StringBuffer(); + + @NonNull String mIndentBuffer = " "; /** @@ -26,7 +31,7 @@ public class StringSerializer { * @param indent the indentation level to use * @param content content to append */ - public void append(int indent, String content) { + public void append(int indent, @Nullable String content) { String indentation = mIndentBuffer.substring(0, indent); mBuffer.append(indentation); mBuffer.append(indentation); @@ -44,6 +49,7 @@ public class StringSerializer { * * @return string representation */ + @NonNull @Override public String toString() { return mBuffer.toString(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java index f2ccb401ea8f..a95a175d0edd 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.utilities; +import android.annotation.NonNull; + import java.util.Arrays; /** Utilities for string manipulation */ @@ -30,6 +32,7 @@ public class StringUtils { * @param post character to pad width 0 = no pad typically ' ' or '0' * @return */ + @NonNull public static String floatToString( float value, int beforeDecimalPoint, int afterDecimalPoint, char pre, char post) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java index 60a59cf464cd..1343345df6e5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.utilities.easing; +import android.annotation.NonNull; + class CubicEasing extends Easing { float mX1 = 0f; float mY1 = 0f; @@ -62,7 +64,7 @@ class CubicEasing extends Easing { mType = type; } - void setup(float[] values) { + void setup(@NonNull float[] values) { setup(values[0], values[1], values[2], values[3]); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java index a29b8af5fbd1..ebb22b6e98c5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java @@ -15,6 +15,9 @@ */ package com.android.internal.widget.remotecompose.core.operations.utilities.easing; +import android.annotation.NonNull; +import android.annotation.Nullable; + /** Support Animation of the FloatExpression */ public class FloatAnimation extends Easing { float[] mSpec; @@ -31,6 +34,7 @@ public class FloatAnimation extends Easing { // private float mScale = 1; float mOffset = 0; + @NonNull @Override public String toString() { @@ -74,7 +78,7 @@ public class FloatAnimation extends Easing { * @return */ public static float[] packToFloatArray( - float duration, int type, float[] spec, float initialValue, float wrap) { + float duration, int type, @Nullable float[] spec, float initialValue, float wrap) { int count = 0; if (!Float.isNaN(initialValue)) { @@ -129,6 +133,90 @@ public class FloatAnimation extends Easing { } /** + * Useful to debug the packed form of an animation string + * + * @param description + * @return + */ + public static String unpackAnimationToString(float[] description) { + float[] mSpec = description; + float mDuration = (mSpec.length == 0) ? 1 : mSpec[0]; + int len = 0; + int type = 0; + float wrapValue = Float.NaN; + float initialValue = Float.NaN; + if (mSpec.length > 1) { + int num_type = Float.floatToRawIntBits(mSpec[1]); + type = num_type & 0xFF; + boolean wrap = ((num_type >> 8) & 0x1) > 0; + boolean init = ((num_type >> 8) & 0x2) > 0; + len = (num_type >> 16) & 0xFFFF; + int off = 2 + len; + if (init) { + initialValue = mSpec[off++]; + } + if (wrap) { + wrapValue = mSpec[off]; + } + } + float[] params = description; + int offset = 2; + + String typeStr = ""; + switch (type) { + case CUBIC_STANDARD: + typeStr = "CUBIC_STANDARD"; + break; + case CUBIC_ACCELERATE: + typeStr = "CUBIC_ACCELERATE"; + break; + case CUBIC_DECELERATE: + typeStr = "CUBIC_DECELERATE"; + break; + case CUBIC_LINEAR: + typeStr = "CUBIC_LINEAR"; + break; + case CUBIC_ANTICIPATE: + typeStr = "CUBIC_ANTICIPATE"; + break; + case CUBIC_OVERSHOOT: + typeStr = "CUBIC_OVERSHOOT"; + + break; + case CUBIC_CUSTOM: + typeStr = "CUBIC_CUSTOM ("; + typeStr += params[offset + 0] + " "; + typeStr += params[offset + 1] + " "; + typeStr += params[offset + 2] + " "; + typeStr += params[offset + 3] + " )"; + break; + case EASE_OUT_BOUNCE: + typeStr = "EASE_OUT_BOUNCE"; + + break; + case EASE_OUT_ELASTIC: + typeStr = "EASE_OUT_ELASTIC"; + break; + case SPLINE_CUSTOM: + typeStr = "SPLINE_CUSTOM ("; + for (int i = offset; i < offset + len; i++) { + typeStr += params[i] + " "; + } + typeStr += ")"; + break; + } + + String str = mDuration + " " + typeStr; + if (!Float.isNaN(initialValue)) { + str += " init =" + initialValue; + } + if (!Float.isNaN(wrapValue)) { + str += " wrap =" + wrapValue; + } + return str; + } + + /** * Create an animation based on a float encoding of the animation * * @param description @@ -208,21 +296,43 @@ public class FloatAnimation extends Easing { setScaleOffset(); } + private static float wrap(float wrap, float value) { + value = value % wrap; + if (value < 0) { + value += wrap; + } + return value; + } + + float wrapDistance(float wrap, float from, float to) { + float delta = (to - from) % 360; + if (delta < -wrap / 2) { + delta += wrap; + } else if (delta > wrap / 2) { + delta -= wrap; + } + return delta; + } + /** * Set the target value to interpolate to * * @param value */ public void setTargetValue(float value) { - if (Float.isNaN(mWrap)) { - mTargetValue = value; - } else { - if (Math.abs((value % mWrap) + mWrap - mInitialValue) - < Math.abs((value % mWrap) - mInitialValue)) { - mTargetValue = (value % mWrap) + mWrap; + mTargetValue = value; + if (!Float.isNaN(mWrap)) { + mInitialValue = wrap(mWrap, mInitialValue); + mTargetValue = wrap(mWrap, mTargetValue); + if (Float.isNaN(mInitialValue)) { + mInitialValue = mTargetValue; + } - } else { - mTargetValue = value % mWrap; + float dist = wrapDistance(mWrap, mInitialValue, mTargetValue); + if ((dist > 0) && (mTargetValue < mInitialValue)) { + mTargetValue += mWrap; + } else if ((dist < 0) && (mTargetValue > mInitialValue)) { + mTargetValue -= mWrap; } } setScaleOffset(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java index 75a60324aa3c..90b65bf2353a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java @@ -15,10 +15,12 @@ */ package com.android.internal.widget.remotecompose.core.operations.utilities.easing; +import android.annotation.NonNull; + /** Provides and interface to create easing functions */ public class GeneralEasing extends Easing { float[] mEasingData = new float[0]; - Easing mEasingCurve = new CubicEasing(CUBIC_STANDARD); + @NonNull Easing mEasingCurve = new CubicEasing(CUBIC_STANDARD); /** * Set the curve based on the float encoding of it diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java index 9355cacde4ad..f540e7008471 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.utilities.easing; +import android.annotation.NonNull; + import java.util.Arrays; /** This performs a spline interpolation in multiple dimensions */ @@ -32,7 +34,7 @@ public class MonotonicCurveFit { * @param time the point along the curve * @param y the parameter at those points */ - public MonotonicCurveFit(double[] time, double[][] y) { + public MonotonicCurveFit(@NonNull double[] time, @NonNull double[][] y) { final int n = time.length; final int dim = y[0].length; mSlopeTemp = new double[dim]; @@ -331,7 +333,8 @@ public class MonotonicCurveFit { } /** This builds a monotonic spline to be used as a wave function */ - public static MonotonicCurveFit buildWave(String configString) { + @NonNull + public static MonotonicCurveFit buildWave(@NonNull String configString) { // done this way for efficiency String str = configString; double[] values = new double[str.length() / 2]; @@ -350,7 +353,8 @@ public class MonotonicCurveFit { return buildWave(Arrays.copyOf(values, count)); } - private static MonotonicCurveFit buildWave(double[] values) { + @NonNull + private static MonotonicCurveFit buildWave(@NonNull double[] values) { int length = values.length * 3 - 2; int len = values.length - 1; double gap = 1.0 / len; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java index b4596897a44f..c7be3cab4c0b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.utilities.easing; +import android.annotation.NonNull; + /** * This class translates a series of floating point values into a continuous curve for use in an * easing function including quantize functions it is used with the "spline(0,0.3,0.3,0.5,...0.9,1)" @@ -28,6 +30,7 @@ public class StepCurve extends Easing { mCurveFit = genSpline(params, offset, len); } + @NonNull private static MonotonicCurveFit genSpline(float[] values, int off, int arrayLen) { int length = arrayLen * 3 - 2; int len = arrayLen - 1; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java new file mode 100644 index 000000000000..3e24372f9b8c --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java @@ -0,0 +1,341 @@ +/* + * 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.internal.widget.remotecompose.core.operations.utilities.touch; + +/* + * 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. + */ + +public class VelocityEasing { + private float mStartPos = 0; + private float mStartV = 0; + private float mEndPos = 0; + private float mDuration = 0; + + private Stage[] mStage = {new Stage(1), new Stage(2), new Stage(3)}; + private int mNumberOfStages = 0; + private Easing mEasing; + private double mEasingAdapterDistance = 0; + private double mEasingAdapterA = 0; + private double mEasingAdapterB = 0; + private boolean mOneDimension = true; + private float mTotalEasingDuration = 0; + + public float getDuration() { + if (mEasing != null) { + return mTotalEasingDuration; + } + return mDuration; + } + + public float getV(float t) { + if (mEasing == null) { + for (int i = 0; i < mNumberOfStages; i++) { + if (mStage[i].mEndTime > t) { + return mStage[i].getVel(t); + } + } + return 0f; + } + int lastStages = mNumberOfStages - 1; + for (int i = 0; i < lastStages; i++) { + if (mStage[i].mEndTime > t) { + return mStage[i].getVel(t); + } + } + return (float) getEasingDiff((t - mStage[lastStages].mStartTime)); + } + + public float getPos(float t) { + if (mEasing == null) { + for (int i = 0; i < mNumberOfStages; i++) { + if (mStage[i].mEndTime > t) { + return mStage[i].getPos(t); + } + } + return mEndPos; + } + int lastStages = mNumberOfStages - 1; + for (int i = 0; i < lastStages; i++) { + if (mStage[i].mEndTime > t) { + return mStage[i].getPos(t); + } + } + var ret = (float) getEasing((t - mStage[lastStages].mStartTime)); + ret += mStage[lastStages].mStartPos; + return ret; + } + + public String toString() { + var s = " "; + for (int i = 0; i < mNumberOfStages; i++) { + Stage stage = mStage[i]; + s += " $i $stage"; + } + return s; + } + + public void config( + float currentPos, + float destination, + float currentVelocity, + float maxTime, + float maxAcceleration, + float maxVelocity, + Easing easing) { + float pos = currentPos; + float velocity = currentVelocity; + if (pos == destination) { + pos += 1f; + } + mStartPos = pos; + mEndPos = destination; + if (easing != null) { + this.mEasing = easing.clone(); + } + float dir = Math.signum(destination - pos); + float maxV = maxVelocity * dir; + float maxA = maxAcceleration * dir; + if (velocity == 0.0) { + velocity = 0.0001f * dir; + } + mStartV = velocity; + if (!rampDown(pos, destination, velocity, maxTime)) { + if (!(mOneDimension + && cruseThenRampDown(pos, destination, velocity, maxTime, maxA, maxV))) { + if (!rampUpRampDown(pos, destination, velocity, maxA, maxV, maxTime)) { + rampUpCruseRampDown(pos, destination, velocity, maxA, maxV, maxTime); + } + } + } + if (mOneDimension) { + configureEasingAdapter(); + } + } + + private boolean rampDown( + float currentPos, float destination, float currentVelocity, float maxTime) { + float timeToDestination = 2 * ((destination - currentPos) / currentVelocity); + if (timeToDestination > 0 && timeToDestination <= maxTime) { // hit the brakes + mNumberOfStages = 1; + mStage[0].setUp(currentVelocity, currentPos, 0f, 0f, destination, timeToDestination); + mDuration = timeToDestination; + return true; + } + return false; + } + + private boolean cruseThenRampDown( + float currentPos, + float destination, + float currentVelocity, + float maxTime, + float maxA, + float maxV) { + float timeToBreak = currentVelocity / maxA; + float brakeDist = currentVelocity * timeToBreak / 2; + float cruseDist = destination - currentPos - brakeDist; + float cruseTime = cruseDist / currentVelocity; + float totalTime = cruseTime + timeToBreak; + if (totalTime > 0 && totalTime < maxTime) { + mNumberOfStages = 2; + mStage[0].setUp(currentVelocity, currentPos, 0f, currentVelocity, cruseDist, cruseTime); + mStage[1].setUp( + currentVelocity, + currentPos + cruseDist, + cruseTime, + 0f, + destination, + cruseTime + timeToBreak); + mDuration = cruseTime + timeToBreak; + return true; + } + return false; + } + + private boolean rampUpRampDown( + float currentPos, + float destination, + float currentVelocity, + float maxA, + float maxVelocity, + float maxTime) { + float peak_v = + Math.signum(maxA) + * (float) + Math.sqrt( + (maxA * (destination - currentPos) + + currentVelocity * currentVelocity / 2)); + if (maxVelocity / peak_v > 1) { + float t1 = (peak_v - currentVelocity) / maxA; + float d1 = (peak_v + currentVelocity) * t1 / 2 + currentPos; + float t2 = peak_v / maxA; + mNumberOfStages = 2; + mStage[0].setUp(currentVelocity, currentPos, 0f, peak_v, d1, t1); + mStage[1].setUp(peak_v, d1, t1, 0f, destination, t2 + t1); + mDuration = t2 + t1; + if (mDuration > maxTime) { + return false; + } + if (mDuration < maxTime / 2) { + t1 = mDuration / 2; + t2 = t1; + peak_v = (2 * (destination - currentPos) / t1 - currentVelocity) / 2; + d1 = (peak_v + currentVelocity) * t1 / 2 + currentPos; + mNumberOfStages = 2; + mStage[0].setUp(currentVelocity, currentPos, 0f, peak_v, d1, t1); + mStage[1].setUp(peak_v, d1, t1, 0f, destination, t2 + t1); + mDuration = t2 + t1; + if (mDuration > maxTime) { + System.out.println(" fail "); + return false; + } + } + return true; + } + return false; + } + + private void rampUpCruseRampDown( + float currentPos, + float destination, + float currentVelocity, + float maxA, + float maxV, + float maxTime) { + float t1 = maxTime / 3; + float t2 = t1 * 2; + float distance = destination - currentPos; + float dt2 = t2 - t1; + float dt3 = maxTime - t2; + float v1 = (2 * distance - currentVelocity * t1) / (t1 + 2 * dt2 + dt3); + mDuration = maxTime; + float d1 = (currentVelocity + v1) * t1 / 2; + float d2 = (v1 + v1) * (t2 - t1) / 2; + mNumberOfStages = 3; + float acc = (v1 - currentVelocity) / t1; + float dec = v1 / dt3; + mStage[0].setUp(currentVelocity, currentPos, 0f, v1, currentPos + d1, t1); + mStage[1].setUp(v1, currentPos + d1, t1, v1, currentPos + d1 + d2, t2); + mStage[2].setUp(v1, currentPos + d1 + d2, t2, 0f, destination, maxTime); + mDuration = maxTime; + } + + double getEasing(double t) { + double gx = t * t * mEasingAdapterA + t * mEasingAdapterB; + if (gx > 1) { + return mEasingAdapterDistance; + } else { + return mEasing.get(gx) * mEasingAdapterDistance; + } + } + + private double getEasingDiff(double t) { + double gx = t * t * mEasingAdapterA + t * mEasingAdapterB; + if (gx > 1) { + return 0.0; + } else { + return mEasing.getDiff(gx) + * mEasingAdapterDistance + * (t * mEasingAdapterA + mEasingAdapterB); + } + } + + protected void configureEasingAdapter() { + if (mEasing == null) { + return; + } + int last = mNumberOfStages - 1; + float initialVelocity = mStage[last].mStartV; + float distance = mStage[last].mEndPos - mStage[last].mStartPos; + float duration = mStage[last].mEndTime - mStage[last].mStartTime; + double baseVel = mEasing.getDiff(0.0); + mEasingAdapterB = initialVelocity / (baseVel * distance); + mEasingAdapterA = 1 - mEasingAdapterB; + mEasingAdapterDistance = distance; + double easingDuration = + (Math.sqrt(4 * mEasingAdapterA + mEasingAdapterB * mEasingAdapterB) + - mEasingAdapterB) + / (2 * mEasingAdapterA); + mTotalEasingDuration = (float) (easingDuration + mStage[last].mStartTime); + } + + interface Easing { + double get(double t); + + double getDiff(double t); + + Easing clone(); + } + + class Stage { + private float mStartV = 0; + private float mStartPos = 0; + private float mStartTime = 0; + private float mEndV = 0; + private float mEndPos = 0; + private float mEndTime = 0; + private float mDeltaV = 0; + private float mDeltaT = 0; + final int mStage; + + Stage(int n) { + mStage = n; + } + + void setUp( + float startV, + float startPos, + float startTime, + float endV, + float endPos, + float endTime) { + this.mStartV = startV; + this.mStartPos = startPos; + this.mStartTime = startTime; + this.mEndV = endV; + this.mEndTime = endTime; + this.mEndPos = endPos; + mDeltaV = this.mEndV - this.mStartV; + mDeltaT = this.mEndTime - this.mStartTime; + } + + float getPos(float t) { + float dt = t - mStartTime; + float pt = dt / mDeltaT; + float v = mStartV + mDeltaV * pt; + return dt * (mStartV + v) / 2 + mStartPos; + } + + float getVel(float t) { + float dt = t - mStartTime; + float pt = dt / (mEndTime - mStartTime); + return mStartV + mDeltaV * pt; + } + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java index 57a804284f0d..3fba8acf8bca 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java +++ b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.types; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.BYTE; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -47,23 +49,26 @@ public class BooleanConstant implements Operation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mId, mValue); } @Override public void apply(RemoteContext context) {} + @NonNull @Override public String deepToString(String indent) { return toString(); } + @NonNull @Override public String toString() { return "BooleanConstant[" + mId + "] = " + mValue + ""; } + @NonNull public static String name() { return "OrigamiBoolean"; } @@ -79,20 +84,20 @@ public class BooleanConstant implements Operation { * @param id * @param value */ - public static void apply(WireBuffer buffer, int id, boolean value) { + public static void apply(@NonNull WireBuffer buffer, int id, boolean value) { buffer.start(OP_CODE); buffer.writeInt(id); buffer.writeBoolean(value); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int id = buffer.readInt(); boolean value = buffer.readBoolean(); operations.add(new BooleanConstant(id, value)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Expressions Operations", OP_CODE, "BooleanConstant") .description("A boolean and its associated id") .field(DocumentedOperation.INT, "id", "id of Int") diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java index 3ef9db9de915..79f2a8d8dec5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java +++ b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.types; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -37,25 +39,28 @@ public class IntegerConstant implements Operation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mId, mValue); } @Override - public void apply(RemoteContext context) { + public void apply(@NonNull RemoteContext context) { context.loadInteger(mId, mValue); } + @NonNull @Override public String deepToString(String indent) { return toString(); } + @NonNull @Override public String toString() { return "IntegerConstant[" + mId + "] = " + mValue + ""; } + @NonNull public static String name() { return "IntegerConstant"; } @@ -71,20 +76,20 @@ public class IntegerConstant implements Operation { * @param textId * @param value */ - public static void apply(WireBuffer buffer, int textId, int value) { + public static void apply(@NonNull WireBuffer buffer, int textId, int value) { buffer.start(Operations.DATA_INT); buffer.writeInt(textId); buffer.writeInt(value); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int id = buffer.readInt(); int value = buffer.readInt(); operations.add(new IntegerConstant(id, value)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Expressions Operations", id(), "IntegerConstant") .description("A integer and its associated id") .field(DocumentedOperation.INT, "id", "id of Int") diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java index 6d51d194708f..01672b469728 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java +++ b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java @@ -17,6 +17,8 @@ package com.android.internal.widget.remotecompose.core.types; import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.LONG; +import android.annotation.NonNull; + import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -47,7 +49,7 @@ public class LongConstant implements Operation { } @Override - public void write(WireBuffer buffer) { + public void write(@NonNull WireBuffer buffer) { apply(buffer, mId, mValue); } @@ -56,11 +58,13 @@ public class LongConstant implements Operation { context.putObject(mId, this); } + @NonNull @Override public String deepToString(String indent) { return toString(); } + @NonNull @Override public String toString() { return "LongConstant[" + mId + "] = " + mValue + ""; @@ -73,20 +77,20 @@ public class LongConstant implements Operation { * @param id * @param value */ - public static void apply(WireBuffer buffer, int id, long value) { + public static void apply(@NonNull WireBuffer buffer, int id, long value) { buffer.start(OP_CODE); buffer.writeInt(id); buffer.writeLong(value); } - public static void read(WireBuffer buffer, List<Operation> operations) { + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { int id = buffer.readInt(); long value = buffer.readLong(); operations.add(new LongConstant(id, value)); } - public static void documentation(DocumentationBuilder doc) { + public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Expressions Operations", OP_CODE, "LongConstant") .description("A boolean and its associated id") .field(DocumentedOperation.INT, "id", "id of Int") diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java index 906282c1dde3..aaee9c565fbb 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java @@ -112,6 +112,16 @@ public class RemoteComposeDocument { } /** + * Gets a array of Names of the named variables of a specific type defined in the doc. + * + * @param type the type of variable NamedVariable.COLOR_TYPE, STRING_TYPE, etc + * @return array of name or null + */ + public String[] getNamedVariables(int type) { + return mDocument.getNamedVariables(type); + } + + /** * Return a component associated with id * * @param id the component id diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java index 06bf4cdb0a0d..cc74b119866d 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java @@ -21,11 +21,14 @@ import android.graphics.Color; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; +import android.view.HapticFeedbackConstants; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.ScrollView; +import com.android.internal.widget.remotecompose.core.CoreDocument; +import com.android.internal.widget.remotecompose.core.operations.NamedVariable; import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior; import com.android.internal.widget.remotecompose.player.platform.RemoteComposeCanvas; @@ -57,11 +60,7 @@ public class RemoteComposePlayer extends FrameLayout { * @param debugFlags 1 to set debug on */ public void setDebug(int debugFlags) { - if (debugFlags == 1) { - mInner.setDebug(true); - } else { - mInner.setDebug(false); - } + mInner.setDebug(debugFlags); } public RemoteComposeDocument getDocument() { @@ -82,6 +81,14 @@ public class RemoteComposePlayer extends FrameLayout { mInner.setDocument(null); } mapColors(); + mInner.setHapticEngine( + new CoreDocument.HapticEngine() { + + @Override + public void haptic(int type) { + provideHapticFeedback(type); + } + }); } /** @@ -259,13 +266,40 @@ public class RemoteComposePlayer extends FrameLayout { /** * This returns a list of colors that have names in the Document. * - * @return + * @return the names of named Strings or null */ public String[] getNamedColors() { return mInner.getNamedColors(); } /** + * This returns a list of floats that have names in the Document. + * + * @return return the names of named floats in the document + */ + public String[] getNamedFloats() { + return mInner.getNamedVariables(NamedVariable.FLOAT_TYPE); + } + + /** + * This returns a list of string name that have names in the Document. + * + * @return the name of named string (not the string itself) + */ + public String[] getNamedStrings() { + return mInner.getNamedVariables(NamedVariable.STRING_TYPE); + } + + /** + * This returns a list of images that have names in the Document. + * + * @return + */ + public String[] getNamedImages() { + return mInner.getNamedVariables(NamedVariable.IMAGE_TYPE); + } + + /** * This sets a color based on its name. Overriding the color set in the document. * * @param colorName Name of the color @@ -481,4 +515,32 @@ public class RemoteComposePlayer extends FrameLayout { return color; } } + + private static int[] sHapticTable = { + HapticFeedbackConstants.NO_HAPTICS, + HapticFeedbackConstants.LONG_PRESS, + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.KEYBOARD_TAP, + HapticFeedbackConstants.CLOCK_TICK, + HapticFeedbackConstants.CONTEXT_CLICK, + HapticFeedbackConstants.KEYBOARD_PRESS, + HapticFeedbackConstants.KEYBOARD_RELEASE, + HapticFeedbackConstants.VIRTUAL_KEY_RELEASE, + HapticFeedbackConstants.TEXT_HANDLE_MOVE, + HapticFeedbackConstants.GESTURE_START, + HapticFeedbackConstants.GESTURE_END, + HapticFeedbackConstants.CONFIRM, + HapticFeedbackConstants.REJECT, + HapticFeedbackConstants.TOGGLE_ON, + HapticFeedbackConstants.TOGGLE_OFF, + HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE, + HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE, + HapticFeedbackConstants.DRAG_START, + HapticFeedbackConstants.SEGMENT_TICK, + HapticFeedbackConstants.SEGMENT_FREQUENT_TICK, + }; + + private void provideHapticFeedback(int type) { + performHapticFeedback(sHapticTable[type % sHapticTable.length]); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java index f59a0d3fa015..0b650a93c9db 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java @@ -26,6 +26,8 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.RenderEffect; +import android.graphics.RenderNode; import android.graphics.RuntimeShader; import android.graphics.Shader; import android.graphics.SweepGradient; @@ -51,6 +53,8 @@ public class AndroidPaintContext extends PaintContext { List<Paint> mPaintList = new ArrayList<>(); Canvas mCanvas; Rect mTmpRect = new Rect(); // use in calculation of bounds + RenderNode mNode = null; + Canvas mPreviousCanvas = null; public AndroidPaintContext(RemoteContext context, Canvas canvas) { super(context); @@ -122,6 +126,53 @@ public class AndroidPaintContext extends PaintContext { } @Override + public void startGraphicsLayer(int w, int h) { + mNode = new RenderNode("layer"); + mNode.setPosition(0, 0, w, h); + mPreviousCanvas = mCanvas; + mCanvas = mNode.beginRecording(); + } + + @Override + public void setGraphicsLayer( + float scaleX, + float scaleY, + float rotationX, + float rotationY, + float rotationZ, + float shadowElevation, + float transformOriginX, + float transformOriginY, + float alpha, + int renderEffectId) { + if (mNode == null) { + return; + } + mNode.setScaleX(scaleX); + mNode.setScaleY(scaleY); + mNode.setRotationX(rotationX); + mNode.setRotationY(rotationY); + mNode.setRotationZ(rotationZ); + mNode.setPivotX(transformOriginX * mNode.getWidth()); + mNode.setPivotY(transformOriginY * mNode.getHeight()); + mNode.setAlpha(alpha); + if (renderEffectId == 1) { + + RenderEffect effect = RenderEffect.createBlurEffect(8f, 8f, Shader.TileMode.CLAMP); + mNode.setRenderEffect(effect); + } + } + + @Override + public void endGraphicsLayer() { + mNode.endRecording(); + mCanvas = mPreviousCanvas; + mCanvas.drawRenderNode(mNode); + // node.discardDisplayList(); + mNode = null; + } + + @Override public void translate(float translateX, float translateY) { mCanvas.translate(translateX, translateY); } @@ -241,6 +292,8 @@ public class AndroidPaintContext extends PaintContext { if (start != 0) { textToPaint = textToPaint.substring(start); } + } else if (end > textToPaint.length()) { + textToPaint = textToPaint.substring(start); } else { textToPaint = textToPaint.substring(start, end); } diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java index f9b22a25ceab..f28e85a44c1b 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java @@ -18,6 +18,7 @@ package com.android.internal.widget.remotecompose.player.platform; import android.graphics.Bitmap; import android.graphics.Path; import android.graphics.PathIterator; +import android.util.Log; import com.android.internal.widget.remotecompose.core.Platform; import com.android.internal.widget.remotecompose.core.operations.PathData; @@ -27,6 +28,8 @@ import java.util.Arrays; /** Services that are needed to be provided by the platform during encoding. */ public class AndroidPlatformServices implements Platform { + private static final String LOG_TAG = "RemoteCompose"; + @Override public byte[] imageToByteArray(Object image) { if (image instanceof Bitmap) { @@ -67,6 +70,24 @@ public class AndroidPlatformServices implements Platform { return null; } + @Override + public void log(LogCategory category, String message) { + switch (category) { + case DEBUG: + Log.d(LOG_TAG, message); + break; + case INFO: + Log.i(LOG_TAG, message); + break; + case WARN: + Log.w(LOG_TAG, message); + break; + default: + Log.e(LOG_TAG, message); + break; + } + } + private float[] androidPathToFloatArray(Path path) { PathIterator i = path.getPathIterator(); int estimatedSize = 0; diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java index e7c0cc8a915d..7a7edba160c8 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java @@ -20,6 +20,7 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.TouchListener; import com.android.internal.widget.remotecompose.core.VariableSupport; import com.android.internal.widget.remotecompose.core.operations.FloatExpression; import com.android.internal.widget.remotecompose.core.operations.ShaderData; @@ -143,9 +144,9 @@ class AndroidRemoteContext extends RemoteContext { } @Override - public void runNamedAction(int id) { + public void runNamedAction(int id, Object value) { String text = getText(id); - mDocument.runNamedAction(text); + mDocument.runNamedAction(text, value); } /** @@ -200,6 +201,11 @@ class AndroidRemoteContext extends RemoteContext { } @Override + public void overrideFloat(int id, float value) { + mRemoteComposeState.overrideFloat(id, value); + } + + @Override public void loadInteger(int id, int value) { mRemoteComposeState.updateInteger(id, value); } @@ -268,6 +274,11 @@ class AndroidRemoteContext extends RemoteContext { return (ShaderData) mRemoteComposeState.getFromId(id); } + @Override + public void addTouchListener(TouchListener touchExpression) { + mDocument.addTouchListener(touchExpression); + } + /////////////////////////////////////////////////////////////////////////////////////////////// // Click handling /////////////////////////////////////////////////////////////////////////////////////////////// @@ -285,4 +296,8 @@ class AndroidRemoteContext extends RemoteContext { String metadata = (String) mRemoteComposeState.getFromId(metadataId); mDocument.addClickArea(id, contentDescription, left, top, right, bottom, metadata); } + + public void hapticEffect(int type) { + mDocument.haptic(type); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java index 7de6988157b7..b54ed8a77ec5 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java @@ -21,6 +21,7 @@ import android.graphics.Color; import android.graphics.Point; import android.util.AttributeSet; import android.view.MotionEvent; +import android.view.VelocityTracker; import android.view.View; import android.widget.FrameLayout; @@ -38,7 +39,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta RemoteComposeDocument mDocument = null; int mTheme = Theme.LIGHT; boolean mInActionDown = false; - boolean mDebug = false; + int mDebug = 0; boolean mHasClickAreas = false; Point mActionDownPoint = new Point(0, 0); AndroidRemoteContext mARContext = new AndroidRemoteContext(); @@ -65,14 +66,14 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta } } - public void setDebug(boolean value) { + public void setDebug(int value) { if (mDebug != value) { mDebug = value; if (USE_VIEW_AREA_CLICK) { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (child instanceof ClickAreaView) { - ((ClickAreaView) child).setDebug(mDebug); + ((ClickAreaView) child).setDebug(mDebug == 1); } } } @@ -107,7 +108,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta ClickAreaView viewArea = new ClickAreaView( getContext(), - mDebug, + mDebug == 1, area.getId(), area.getContentDescription(), area.getMetadata()); @@ -128,6 +129,10 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta } } + public void setHapticEngine(CoreDocument.HapticEngine engine) { + mDocument.getDocument().setHapticEngine(engine); + } + @Override public void onViewDetachedFromWindow(View view) { removeAllViews(); @@ -138,6 +143,16 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta } /** + * Gets a array of Names of the named variables of a specific type defined in the loaded doc. + * + * @param type the type of variable NamedVariable.COLOR_TYPE, STRING_TYPE, etc + * @return array of name or null + */ + public String[] getNamedVariables(int type) { + return mDocument.getNamedVariables(type); + } + + /** * set the color associated with this name. * * @param colorName Name of color typically "android.xxx" @@ -198,7 +213,12 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta this.mTheme = theme; } + private VelocityTracker mVelocityTracker = null; + public boolean onTouchEvent(MotionEvent event) { + int index = event.getActionIndex(); + int action = event.getActionMasked(); + int pointerId = event.getPointerId(index); if (USE_VIEW_AREA_CLICK && mHasClickAreas) { return super.onTouchEvent(event); } @@ -207,15 +227,51 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta mActionDownPoint.x = (int) event.getX(); mActionDownPoint.y = (int) event.getY(); mInActionDown = true; + CoreDocument doc = mDocument.getDocument(); + if (doc.hasTouchListener()) { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } else { + mVelocityTracker.clear(); + } + mVelocityTracker.addMovement(event); + doc.touchDown(mARContext, event.getX(), event.getY()); + } return true; + case MotionEvent.ACTION_CANCEL: mInActionDown = false; + doc = mDocument.getDocument(); + if (doc.hasTouchListener()) { + mVelocityTracker.computeCurrentVelocity(1000); + float dx = mVelocityTracker.getXVelocity(pointerId); + float dy = mVelocityTracker.getYVelocity(pointerId); + doc.touchCancel(mARContext, event.getX(), event.getY(), dx, dy); + } return true; case MotionEvent.ACTION_UP: mInActionDown = false; performClick(); + doc = mDocument.getDocument(); + if (doc.hasTouchListener()) { + mVelocityTracker.computeCurrentVelocity(1000); + float dx = mVelocityTracker.getXVelocity(pointerId); + float dy = mVelocityTracker.getYVelocity(pointerId); + doc.touchUp(mARContext, event.getX(), event.getY(), dx, dy); + } return true; + case MotionEvent.ACTION_MOVE: + if (mInActionDown) { + if (mVelocityTracker != null) { + mVelocityTracker.addMovement(event); + doc = mDocument.getDocument(); + boolean repaint = doc.touchDrag(mARContext, event.getX(), event.getY()); + if (repaint) { + invalidate(); + } + } + } } return false; } @@ -292,7 +348,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta mARContext.mWidth = getWidth(); mARContext.mHeight = getHeight(); mDocument.paint(mARContext, mTheme); - if (mDebug) { + if (mDebug == 1) { mCount++; if (System.nanoTime() - mTime > 1000000000L) { System.out.println(" count " + mCount + " fps"); diff --git a/core/res/Android.bp b/core/res/Android.bp index 66c2e12f7cdf..0e4e22b09e24 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -158,6 +158,7 @@ android_app { flags_packages: [ "android.app.appfunctions.flags-aconfig", "android.app.contextualsearch.flags-aconfig", + "android.app.flags-aconfig", "android.appwidget.flags-aconfig", "android.content.pm.flags-aconfig", "android.provider.flags-aconfig", @@ -171,7 +172,9 @@ android_app { "android.security.flags-aconfig", "com.android.hardware.input.input-aconfig", "aconfig_trade_in_mode_flags", + "art-aconfig-flags", "ranging_aconfig_flags", + "aconfig_settingslib_flags", ], } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5913992004b8..d09802a91edc 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2632,13 +2632,22 @@ <!-- @SystemApi Allows access to perform vendor effects in the vibrator. <p>Protection level: signature - @FlaggedApi("android.os.vibrator.vendor_vibration_effects") + @FlaggedApi(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) @hide --> <permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS" android:protectionLevel="signature|privileged" android:featureFlag="android.os.vibrator.vendor_vibration_effects" /> + <!-- @SystemApi Allows access to start a vendor vibration session. + <p>Protection level: signature + @FlaggedApi(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + @hide + --> + <permission android:name="android.permission.START_VIBRATION_SESSIONS" + android:protectionLevel="signature|privileged" + android:featureFlag="android.os.vibrator.vendor_vibration_effects" /> + <!-- @SystemApi Allows access to the vibrator state. <p>Protection level: signature @hide @@ -4454,6 +4463,18 @@ android:description="@string/permdesc_hideOverlayWindows" android:protectionLevel="normal" /> + <!-- Allows an app to enter Picture-in-Picture mode when the user is not explicitly requesting + it. This includes using {@link PictureInPictureParams.Builder#setAutoEnterEnabled} as well + as lifecycle methods such as {@link Activity#onUserLeaveHint} and {@link Activity#onPause} + to enter PiP when the user leaves the app. + This permission should only be used for certain PiP + <a href="{@docRoot}training/tv/get-started/multitasking#usage-types">usage types</a>. + @FlaggedApi(android.app.Flags.FLAG_ENABLE_TV_IMPLICIT_ENTER_PIP_RESTRICTION) + --> + <permission android:name="android.permission.TV_IMPLICIT_ENTER_PIP" + android:protectionLevel="normal" + android:featureFlag="android.app.enable_tv_implicit_enter_pip_restriction" /> + <!-- ================================== --> <!-- Permissions affecting the system wallpaper --> <!-- ================================== --> @@ -4960,6 +4981,27 @@ <permission android:name="android.permission.PROVIDE_REMOTE_CREDENTIALS" android:protectionLevel="signature|privileged|role" /> + <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_SETTINGS_CATALYST) + Allows an application to access the Settings Preference services to read settings exposed + by the system Settings app and system apps that contribute settings surfaced by the + Settings app. + <p>This allows the calling application to read settings values through the host + application, agnostic of underlying storage. --> + <permission android:name="android.permission.READ_SYSTEM_PREFERENCES" + android:protectionLevel="signature|privileged|role" + android:featureFlag="com.android.settingslib.flags.settings_catalyst" /> + + <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_SETTINGS_CATALYST) + Allows an application to access the Settings Preference services to write settings + values exposed by the system Settings app and system apps that contribute settings surfaced + in the Settings app. + <p>This allows the calling application to write settings values + through the host application, agnostic of underlying storage. + <p>Protection Level: signature|privileged|appop - appop to be added in followup --> + <permission android:name="android.permission.WRITE_SYSTEM_PREFERENCES" + android:protectionLevel="signature|privileged" + android:featureFlag="com.android.settingslib.flags.settings_catalyst" /> + <!-- ========================================= --> <!-- Permissions for special development tools --> <!-- ========================================= --> @@ -8482,6 +8524,16 @@ android:protectionLevel="signature|privileged" android:featureFlag="com.android.tradeinmode.flags.enable_trade_in_mode" /> + <!-- @SystemApi + @FlaggedApi(com.android.art.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS) + Ability to read program metadata and attach dynamic instrumentation. + <p>Protection level: signature + @hide + --> + <permission android:name="android.permission.DYNAMIC_INSTRUMENTATION" + android:protectionLevel="signature" + android:featureFlag="com.android.art.flags.executable_method_file_offsets" /> + <!-- @TestApi Signature permission reserved for testing. This should never be used to diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 4d73f228ad0c..41dec3776b5c 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2273,6 +2273,22 @@ <attr name="enableOnBackInvokedCallback" format="boolean"/> <attr name="intentMatchingFlags"/> + + <!-- Specifies the set of drawable resources that can be used in place + of an existing declared icon or banner for activities that appear + in the app launcher. The resource referenced must be an array of + drawable resources and can contain at most 500 items. + {@link android.content.pm.PackageManager#changeLauncherIconConfig} + @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) --> + <attr name="alternateLauncherIcons" format="reference" /> + + <!-- Specifies the set of string resources that can be used in place + of an existing declared label for activities that appear + in the app launcher. The resource referenced must be an array of + string resources and can contain at most 500 items. + {@link android.content.pm.PackageManager#changeLauncherIconConfig} + @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) --> + <attr name="alternateLauncherLabels" format="reference" /> </declare-styleable> <!-- An attribution is a logical part of an app and is identified by a tag. diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 70cc5f14391d..b6436d0b30a5 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -127,6 +127,10 @@ <public name="intentMatchingFlags"/> <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) --> <public name="layoutLabel"/> + <!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) --> + <public name="alternateLauncherIcons"/> + <!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) --> + <public name="alternateLauncherLabels"/> </staging-public-group> <staging-public-group type="id" first-id="0x01b60000"> diff --git a/core/res/res/values/stoppable_fgs_system_apps.xml b/core/res/res/values/stoppable_fgs_system_apps.xml new file mode 100644 index 000000000000..165ff61c7b3e --- /dev/null +++ b/core/res/res/values/stoppable_fgs_system_apps.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources> + <!-- A list of system apps whose FGS can be stopped in the task manager. --> + <string-array translatable="false" name="stoppable_fgs_system_apps"> + </string-array> + <!-- stoppable_fgs_system_apps which is supposed to be overridden by vendor --> + <string-array translatable="false" name="vendor_stoppable_fgs_system_apps"> + </string-array> +</resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index aa08d5e2313e..db81a3be440f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1306,6 +1306,8 @@ <java-symbol type="array" name="vendor_policy_exempt_apps" /> <java-symbol type="array" name="cloneable_apps" /> <java-symbol type="array" name="config_securityStatePackages" /> + <java-symbol type="array" name="stoppable_fgs_system_apps" /> + <java-symbol type="array" name="vendor_stoppable_fgs_system_apps" /> <java-symbol type="drawable" name="default_wallpaper" /> <java-symbol type="drawable" name="default_lock_wallpaper" /> diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java index 31a4f16553a0..911b7ce22741 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -120,7 +120,8 @@ public class ClientTransactionListenerControllerTest { doReturn(newDisplayInfo).when(mIDisplayManager).getDisplayInfo(123); mDisplayManager.registerDisplayListener(mListener, mHandler, - DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null /* packageName */); + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, + null /* packageName */); mController.onDisplayChanged(123); mHandler.runWithScissors(() -> { }, 0); diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java index d169ce3c07d0..7bc4abd935b6 100644 --- a/core/tests/coretests/src/android/content/IntentTest.java +++ b/core/tests/coretests/src/android/content/IntentTest.java @@ -37,6 +37,10 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** * Build/Install/Run: * atest FrameworksCoreTests:IntentTest @@ -57,7 +61,12 @@ public class IntentTest { public void testReadFromParcelWithExtraIntentKeys() { Intent intent = new Intent("TEST_ACTION"); intent.putExtra(TEST_EXTRA_NAME, new Intent(TEST_ACTION)); + // Not an intent, don't count. intent.putExtra(TEST_EXTRA_NAME + "2", 1); + ArrayList<Intent> intents = new ArrayList<>(); + intents.add(new Intent(TEST_ACTION)); + intent.putParcelableArrayListExtra(TEST_EXTRA_NAME + "3", intents); + intent.setClipData(ClipData.newIntent("label", new Intent(TEST_ACTION))); intent.collectExtraIntentKeys(); final Parcel parcel = Parcel.obtain(); @@ -68,7 +77,7 @@ public class IntentTest { assertEquals(intent.getAction(), target.getAction()); assertEquals(intent.getExtraIntentKeys(), target.getExtraIntentKeys()); - assertThat(intent.getExtraIntentKeys()).hasSize(1); + assertThat(intent.getExtraIntentKeys()).hasSize(3); } @Test @@ -87,13 +96,37 @@ public class IntentTest { @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT) public void testCollectExtraIntentKeys() { Intent intent = new Intent(TEST_ACTION); - Intent extraIntent = new Intent(TEST_ACTION, TEST_URI); - intent.putExtra(TEST_EXTRA_NAME, extraIntent); + + Intent[] intents = new Intent[10]; + for (int i = 0; i < intents.length; i++) { + intents[i] = new Intent("action" + i); + } + Intent[] intents2 = new Intent[2]; // intents[6-7] + System.arraycopy(intents, 6, intents2, 0, intents2.length); + ArrayList<Intent> intents3 = new ArrayList<>(2); + intents3.addAll(Arrays.asList(intents).subList(8, 10)); // intents[8-9] + intent.putExtra("key1", intents[0]); + intent.putExtra("array-key", intents2); + intent.setClipData(ClipData.newIntent("label2", intents[1])); + intent.putExtra("intkey", 1); + intents[0].putExtra("key3", intents[2]); + intents[0].setClipData(ClipData.newIntent("label4", intents[3])); + intents[0].putParcelableArrayListExtra("array-list-key", intents3); + intents[1].putExtra("key3", intents[4]); + intents[1].setClipData(ClipData.newIntent("label4", intents[5])); + intents[5].putExtra("intkey", 2); intent.collectExtraIntentKeys(); - assertThat(intent.getExtraIntentKeys()).hasSize(1); - assertThat(intent.getExtraIntentKeys()).contains(TEST_EXTRA_NAME); + // collect all actions of nested intents. + final List<String> actions = new ArrayList<>(); + intent.forEachNestedCreatorToken(intent1 -> { + actions.add(intent1.getAction()); + }); + assertThat(actions).hasSize(10); + for (int i = 0; i < intents.length; i++) { + assertThat(actions).contains("action" + i); + } } } diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java index 5a0dacb38865..9552c887443b 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java +++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java @@ -55,9 +55,10 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class DisplayManagerGlobalTest { - private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; + private static final long ALL_DISPLAY_EVENTS = + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; @Mock private IDisplayManager mDisplayManager; @@ -127,19 +128,22 @@ public class DisplayManagerGlobalTest { int displayId = 1; mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, - ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED, null); + ALL_DISPLAY_EVENTS + & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); waitForHandler(); Mockito.verifyZeroInteractions(mListener); mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, - ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null); + ALL_DISPLAY_EVENTS + & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, null); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); waitForHandler(); Mockito.verifyZeroInteractions(mListener); mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, - ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null); + ALL_DISPLAY_EVENTS + & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null); callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); waitForHandler(); Mockito.verifyZeroInteractions(mListener); @@ -162,22 +166,25 @@ public class DisplayManagerGlobalTest { public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners() throws RemoteException { mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, - DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, null); + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED, + null); InOrder inOrder = Mockito.inOrder(mDisplayManager); inOrder.verify(mDisplayManager) .registerCallbackWithEventMask(mCallbackCaptor.capture(), - eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + eq(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED)); mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks(); inOrder.verify(mDisplayManager) .registerCallbackWithEventMask(mCallbackCaptor.capture(), - eq(ALL_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + eq(ALL_DISPLAY_EVENTS + | DisplayManagerGlobal + .INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED)); mDisplayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks(); inOrder.verify(mDisplayManager) .registerCallbackWithEventMask(mCallbackCaptor.capture(), - eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + eq(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED)); mDisplayManagerGlobal.unregisterDisplayListener(mListener); inOrder.verify(mDisplayManager) @@ -196,10 +203,12 @@ public class DisplayManagerGlobalTest { // One listener listens on add/remove, and the other one listens on change. mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, - DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null /* packageName */); + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, + null /* packageName */); mDisplayManagerGlobal.registerDisplayListener(mListener2, mHandler, - DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null /* packageName */); + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, + null /* packageName */); mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321); waitForHandler(); diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt new file mode 100644 index 000000000000..a6de611cc077 --- /dev/null +++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt @@ -0,0 +1,472 @@ +/* + * 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.hardware.display + +import android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM +import android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP +import android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT +import android.view.Display +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class DisplayTopologyTest { + private var topology = DisplayTopology() + + @Test + fun addOneDisplay() { + val displayId = 1 + val width = 800f + val height = 600f + + topology.addDisplay(displayId, width, height) + + assertThat(topology.primaryDisplayId).isEqualTo(displayId) + + val display = topology.root!! + assertThat(display.displayId).isEqualTo(displayId) + assertThat(display.width).isEqualTo(width) + assertThat(display.height).isEqualTo(height) + assertThat(display.children).isEmpty() + } + + @Test + fun addTwoDisplays() { + val displayId1 = 1 + val width1 = 800f + val height1 = 600f + + val displayId2 = 2 + val width2 = 1000f + val height2 = 1500f + + topology.addDisplay(displayId1, width1, height1) + topology.addDisplay(displayId2, width2, height2) + + assertThat(topology.primaryDisplayId).isEqualTo(displayId1) + + val display1 = topology.root!! + assertThat(display1.displayId).isEqualTo(displayId1) + assertThat(display1.width).isEqualTo(width1) + assertThat(display1.height).isEqualTo(height1) + assertThat(display1.children).hasSize(1) + + val display2 = display1.children[0] + assertThat(display2.displayId).isEqualTo(displayId2) + assertThat(display2.width).isEqualTo(width2) + assertThat(display2.height).isEqualTo(height2) + assertThat(display2.children).isEmpty() + assertThat(display2.position).isEqualTo(POSITION_TOP) + assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2) + } + + @Test + fun addManyDisplays() { + val displayId1 = 1 + val width1 = 800f + val height1 = 600f + + val displayId2 = 2 + val width2 = 1000f + val height2 = 1500f + + topology.addDisplay(displayId1, width1, height1) + topology.addDisplay(displayId2, width2, height2) + + val noOfDisplays = 30 + for (i in 3..noOfDisplays) { + topology.addDisplay(/* displayId= */ i, width1, height1) + } + + assertThat(topology.primaryDisplayId).isEqualTo(displayId1) + + val display1 = topology.root!! + assertThat(display1.displayId).isEqualTo(displayId1) + assertThat(display1.width).isEqualTo(width1) + assertThat(display1.height).isEqualTo(height1) + assertThat(display1.children).hasSize(1) + + val display2 = display1.children[0] + assertThat(display2.displayId).isEqualTo(displayId2) + assertThat(display2.width).isEqualTo(width2) + assertThat(display2.height).isEqualTo(height2) + assertThat(display2.children).hasSize(1) + assertThat(display2.position).isEqualTo(POSITION_TOP) + assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2) + + var display = display2 + for (i in 3..noOfDisplays) { + display = display.children[0] + assertThat(display.displayId).isEqualTo(i) + assertThat(display.width).isEqualTo(width1) + assertThat(display.height).isEqualTo(height1) + // The last display should have no children + assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0) + assertThat(display.position).isEqualTo(POSITION_RIGHT) + assertThat(display.offset).isEqualTo(0) + } + } + + @Test + fun removeDisplays() { + val displayId1 = 1 + val width1 = 800f + val height1 = 600f + + val displayId2 = 2 + val width2 = 1000f + val height2 = 1500f + + topology.addDisplay(displayId1, width1, height1) + topology.addDisplay(displayId2, width2, height2) + + val noOfDisplays = 30 + for (i in 3..noOfDisplays) { + topology.addDisplay(/* displayId= */ i, width1, height1) + } + + var removedDisplays = arrayOf(20) + topology.removeDisplay(20) + + assertThat(topology.primaryDisplayId).isEqualTo(displayId1) + + var display1 = topology.root!! + assertThat(display1.displayId).isEqualTo(displayId1) + assertThat(display1.width).isEqualTo(width1) + assertThat(display1.height).isEqualTo(height1) + assertThat(display1.children).hasSize(1) + + var display2 = display1.children[0] + assertThat(display2.displayId).isEqualTo(displayId2) + assertThat(display2.width).isEqualTo(width2) + assertThat(display2.height).isEqualTo(height2) + assertThat(display2.children).hasSize(1) + assertThat(display2.position).isEqualTo(POSITION_TOP) + assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2) + + var display = display2 + for (i in 3..noOfDisplays) { + if (i in removedDisplays) { + continue + } + display = display.children[0] + assertThat(display.displayId).isEqualTo(i) + assertThat(display.width).isEqualTo(width1) + assertThat(display.height).isEqualTo(height1) + // The last display should have no children + assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0) + assertThat(display.position).isEqualTo(POSITION_RIGHT) + assertThat(display.offset).isEqualTo(0) + } + + topology.removeDisplay(22) + removedDisplays += 22 + topology.removeDisplay(23) + removedDisplays += 23 + topology.removeDisplay(25) + removedDisplays += 25 + + assertThat(topology.primaryDisplayId).isEqualTo(displayId1) + + display1 = topology.root!! + assertThat(display1.displayId).isEqualTo(displayId1) + assertThat(display1.width).isEqualTo(width1) + assertThat(display1.height).isEqualTo(height1) + assertThat(display1.children).hasSize(1) + + display2 = display1.children[0] + assertThat(display2.displayId).isEqualTo(displayId2) + assertThat(display2.width).isEqualTo(width2) + assertThat(display2.height).isEqualTo(height2) + assertThat(display2.children).hasSize(1) + assertThat(display2.position).isEqualTo(POSITION_TOP) + assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2) + + display = display2 + for (i in 3..noOfDisplays) { + if (i in removedDisplays) { + continue + } + display = display.children[0] + assertThat(display.displayId).isEqualTo(i) + assertThat(display.width).isEqualTo(width1) + assertThat(display.height).isEqualTo(height1) + // The last display should have no children + assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0) + assertThat(display.position).isEqualTo(POSITION_RIGHT) + assertThat(display.offset).isEqualTo(0) + } + } + + @Test + fun removeAllDisplays() { + val displayId = 1 + val width = 800f + val height = 600f + + topology.addDisplay(displayId, width, height) + topology.removeDisplay(displayId) + + assertThat(topology.primaryDisplayId).isEqualTo(Display.INVALID_DISPLAY) + assertThat(topology.root).isNull() + } + + @Test + fun removeDisplayThatDoesNotExist() { + val displayId = 1 + val width = 800f + val height = 600f + + topology.addDisplay(displayId, width, height) + topology.removeDisplay(3) + + assertThat(topology.primaryDisplayId).isEqualTo(displayId) + + val display = topology.root!! + assertThat(display.displayId).isEqualTo(displayId) + assertThat(display.width).isEqualTo(width) + assertThat(display.height).isEqualTo(height) + assertThat(display.children).isEmpty() + } + + @Test + fun removePrimaryDisplay() { + val displayId1 = 1 + val displayId2 = 2 + val width = 800f + val height = 600f + + topology = DisplayTopology(/* root= */ null, displayId2) + topology.addDisplay(displayId1, width, height) + topology.addDisplay(displayId2, width, height) + topology.removeDisplay(displayId2) + + assertThat(topology.primaryDisplayId).isEqualTo(displayId1) + val display = topology.root!! + assertThat(display.displayId).isEqualTo(displayId1) + assertThat(display.width).isEqualTo(width) + assertThat(display.height).isEqualTo(height) + assertThat(display.children).isEmpty() + } + + @Test + fun normalization_noOverlaps_leavesTopologyUnchanged() { + val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, + /* height= */ 600f, /* position= */ 0, /* offset= */ 0f) + + val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f, + /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f) + display1.addChild(display2) + + val primaryDisplayId = 3 + val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, + /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f) + display1.addChild(display3) + + val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f, + /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) + display2.addChild(display4) + + topology = DisplayTopology(display1, primaryDisplayId) + topology.normalize() + + assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId) + + val actualDisplay1 = topology.root!! + assertThat(actualDisplay1.displayId).isEqualTo(1) + assertThat(actualDisplay1.width).isEqualTo(200f) + assertThat(actualDisplay1.height).isEqualTo(600f) + assertThat(actualDisplay1.children).hasSize(2) + + val actualDisplay2 = actualDisplay1.children[0] + assertThat(actualDisplay2.displayId).isEqualTo(2) + assertThat(actualDisplay2.width).isEqualTo(600f) + assertThat(actualDisplay2.height).isEqualTo(200f) + assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay2.offset).isEqualTo(0f) + assertThat(actualDisplay2.children).hasSize(1) + + val actualDisplay3 = actualDisplay1.children[1] + assertThat(actualDisplay3.displayId).isEqualTo(3) + assertThat(actualDisplay3.width).isEqualTo(600f) + assertThat(actualDisplay3.height).isEqualTo(200f) + assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay3.offset).isEqualTo(400f) + assertThat(actualDisplay3.children).isEmpty() + + val actualDisplay4 = actualDisplay2.children[0] + assertThat(actualDisplay4.displayId).isEqualTo(4) + assertThat(actualDisplay4.width).isEqualTo(200f) + assertThat(actualDisplay4.height).isEqualTo(600f) + assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay4.offset).isEqualTo(0f) + assertThat(actualDisplay4.children).isEmpty() + } + + @Test + fun normalization_moveDisplayWithoutReparenting() { + val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, + /* height= */ 600f, /* position= */ 0, /* offset= */ 0f) + + val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f, + /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) + display1.addChild(display2) + + val primaryDisplayId = 3 + val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, + /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f) + display1.addChild(display3) + + val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f, + /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) + display2.addChild(display4) + + topology = DisplayTopology(display1, primaryDisplayId) + // Display 3 becomes a child of display 2. Display 4 gets moved without changing its parent. + topology.normalize() + + assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId) + + val actualDisplay1 = topology.root!! + assertThat(actualDisplay1.displayId).isEqualTo(1) + assertThat(actualDisplay1.width).isEqualTo(200f) + assertThat(actualDisplay1.height).isEqualTo(600f) + assertThat(actualDisplay1.children).hasSize(1) + + val actualDisplay2 = actualDisplay1.children[0] + assertThat(actualDisplay2.displayId).isEqualTo(2) + assertThat(actualDisplay2.width).isEqualTo(200f) + assertThat(actualDisplay2.height).isEqualTo(600f) + assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay2.offset).isEqualTo(0f) + assertThat(actualDisplay2.children).hasSize(2) + + val actualDisplay3 = actualDisplay2.children[1] + assertThat(actualDisplay3.displayId).isEqualTo(3) + assertThat(actualDisplay3.width).isEqualTo(600f) + assertThat(actualDisplay3.height).isEqualTo(200f) + assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay3.offset).isEqualTo(10f) + assertThat(actualDisplay3.children).isEmpty() + + val actualDisplay4 = actualDisplay2.children[0] + assertThat(actualDisplay4.displayId).isEqualTo(4) + assertThat(actualDisplay4.width).isEqualTo(200f) + assertThat(actualDisplay4.height).isEqualTo(600f) + assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay4.offset).isEqualTo(210f) + assertThat(actualDisplay4.children).isEmpty() + } + + @Test + fun normalization_moveDisplayWithoutReparenting_offsetOutOfBounds() { + val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, + /* height= */ 50f, /* position= */ 0, /* offset= */ 0f) + + val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f, + /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f) + display1.addChild(display2) + + val primaryDisplayId = 3 + val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, + /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f) + display1.addChild(display3) + + topology = DisplayTopology(display1, primaryDisplayId) + // Display 3 gets moved and its left side is still on the same line as the right side + // of Display 1, but it no longer touches it (the offset is out of bounds), so Display 2 + // becomes its new parent. + topology.normalize() + + assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId) + + val actualDisplay1 = topology.root!! + assertThat(actualDisplay1.displayId).isEqualTo(1) + assertThat(actualDisplay1.width).isEqualTo(200f) + assertThat(actualDisplay1.height).isEqualTo(50f) + assertThat(actualDisplay1.children).hasSize(1) + + val actualDisplay2 = actualDisplay1.children[0] + assertThat(actualDisplay2.displayId).isEqualTo(2) + assertThat(actualDisplay2.width).isEqualTo(600f) + assertThat(actualDisplay2.height).isEqualTo(200f) + assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay2.offset).isEqualTo(0f) + assertThat(actualDisplay2.children).hasSize(1) + + val actualDisplay3 = actualDisplay2.children[0] + assertThat(actualDisplay3.displayId).isEqualTo(3) + assertThat(actualDisplay3.width).isEqualTo(600f) + assertThat(actualDisplay3.height).isEqualTo(200f) + assertThat(actualDisplay3.position).isEqualTo(POSITION_BOTTOM) + assertThat(actualDisplay3.offset).isEqualTo(0f) + assertThat(actualDisplay3.children).isEmpty() + } + + @Test + fun normalization_moveAndReparentDisplay() { + val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, + /* height= */ 600f, /* position= */ 0, /* offset= */ 0f) + + val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f, + /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) + display1.addChild(display2) + + val primaryDisplayId = 3 + val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, + /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f) + display1.addChild(display3) + + val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f, + /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) + display2.addChild(display4) + + topology = DisplayTopology(display1, primaryDisplayId) + topology.normalize() + + assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId) + + val actualDisplay1 = topology.root!! + assertThat(actualDisplay1.displayId).isEqualTo(1) + assertThat(actualDisplay1.width).isEqualTo(200f) + assertThat(actualDisplay1.height).isEqualTo(600f) + assertThat(actualDisplay1.children).hasSize(1) + + val actualDisplay2 = actualDisplay1.children[0] + assertThat(actualDisplay2.displayId).isEqualTo(2) + assertThat(actualDisplay2.width).isEqualTo(200f) + assertThat(actualDisplay2.height).isEqualTo(600f) + assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay2.offset).isEqualTo(0f) + assertThat(actualDisplay2.children).hasSize(1) + + val actualDisplay3 = actualDisplay2.children[0] + assertThat(actualDisplay3.displayId).isEqualTo(3) + assertThat(actualDisplay3.width).isEqualTo(600f) + assertThat(actualDisplay3.height).isEqualTo(200f) + assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay3.offset).isEqualTo(400f) + assertThat(actualDisplay3.children).hasSize(1) + + val actualDisplay4 = actualDisplay3.children[0] + assertThat(actualDisplay4.displayId).isEqualTo(4) + assertThat(actualDisplay4.width).isEqualTo(200f) + assertThat(actualDisplay4.height).isEqualTo(600f) + assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay4.offset).isEqualTo(-400f) + assertThat(actualDisplay4.children).isEmpty() + } +}
\ No newline at end of file diff --git a/core/tests/coretests/src/android/view/DisplayInfoTest.java b/core/tests/coretests/src/android/view/DisplayInfoTest.java index 4c5b7e508e34..8932cf1ba552 100644 --- a/core/tests/coretests/src/android/view/DisplayInfoTest.java +++ b/core/tests/coretests/src/android/view/DisplayInfoTest.java @@ -78,6 +78,23 @@ public class DisplayInfoTest { } @Test + public void testRefreshRateOverride_keepsDisplyInfosEqualWhenOverrideIsSame() { + Display.Mode mode = new Display.Mode( + /*modeId=*/1, /*width=*/1000, /*height=*/1000, /*refreshRate=*/120); + DisplayInfo displayInfo1 = new DisplayInfo(); + setSupportedMode(displayInfo1, mode); + displayInfo1.renderFrameRate = 60; + displayInfo1.refreshRateOverride = 30; + + DisplayInfo displayInfo2 = new DisplayInfo(); + setSupportedMode(displayInfo2, mode); + displayInfo2.renderFrameRate = 30; + displayInfo2.refreshRateOverride = 30; + + assertTrue(displayInfo1.equals(displayInfo2)); + } + + @Test public void testRefreshRateOverride_makeDisplayInfosDifferent() { Display.Mode mode = new Display.Mode( /*modeId=*/1, /*width=*/1000, /*height=*/1000, /*refreshRate=*/120); diff --git a/core/tests/vibrator/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java index 6210a00a5940..09bfadbf56a4 100644 --- a/core/tests/vibrator/src/android/os/VibratorTest.java +++ b/core/tests/vibrator/src/android/os/VibratorTest.java @@ -110,8 +110,9 @@ public class VibratorTest { @Test public void onVibratorStateChanged_noVibrator_registersNoListenerToVibratorManager() { + int[] vibratorIds = new int[0]; VibratorManager mockVibratorManager = mock(VibratorManager.class); - when(mockVibratorManager.getVibratorIds()).thenReturn(new int[0]); + when(mockVibratorManager.getVibratorIds()).thenReturn(vibratorIds); Vibrator.OnVibratorStateChangedListener mockListener = mock(Vibrator.OnVibratorStateChangedListener.class); @@ -119,7 +120,7 @@ public class VibratorTest { new SystemVibrator.MultiVibratorStateListener( mTestLooper.getNewExecutor(), mockListener); - multiVibratorListener.register(mockVibratorManager); + multiVibratorListener.register(mockVibratorManager, vibratorIds); // Never tries to register a listener to an individual vibrator. assertFalse(multiVibratorListener.hasRegisteredListeners()); @@ -128,8 +129,9 @@ public class VibratorTest { @Test public void onVibratorStateChanged_singleVibrator_forwardsAllCallbacks() { + int[] vibratorIds = new int[] { 1 }; VibratorManager mockVibratorManager = mock(VibratorManager.class); - when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1 }); + when(mockVibratorManager.getVibratorIds()).thenReturn(vibratorIds); when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance()); Vibrator.OnVibratorStateChangedListener mockListener = @@ -138,7 +140,7 @@ public class VibratorTest { new SystemVibrator.MultiVibratorStateListener( mTestLooper.getNewExecutor(), mockListener); - multiVibratorListener.register(mockVibratorManager); + multiVibratorListener.register(mockVibratorManager, vibratorIds); assertTrue(multiVibratorListener.hasRegisteredListeners()); multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); @@ -156,8 +158,9 @@ public class VibratorTest { @Test public void onVibratorStateChanged_multipleVibrators_triggersOnlyWhenAllVibratorsInitialized() { + int[] vibratorIds = new int[] { 1, 2 }; VibratorManager mockVibratorManager = mock(VibratorManager.class); - when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 }); + when(mockVibratorManager.getVibratorIds()).thenReturn(vibratorIds); when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance()); Vibrator.OnVibratorStateChangedListener mockListener = @@ -166,7 +169,7 @@ public class VibratorTest { new SystemVibrator.MultiVibratorStateListener( mTestLooper.getNewExecutor(), mockListener); - multiVibratorListener.register(mockVibratorManager); + multiVibratorListener.register(mockVibratorManager, vibratorIds); assertTrue(multiVibratorListener.hasRegisteredListeners()); multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); @@ -181,8 +184,9 @@ public class VibratorTest { @Test public void onVibratorStateChanged_multipleVibrators_stateChangeIsDeduped() { + int[] vibratorIds = new int[] { 1, 2 }; VibratorManager mockVibratorManager = mock(VibratorManager.class); - when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 }); + when(mockVibratorManager.getVibratorIds()).thenReturn(vibratorIds); when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance()); Vibrator.OnVibratorStateChangedListener mockListener = @@ -191,7 +195,7 @@ public class VibratorTest { new SystemVibrator.MultiVibratorStateListener( mTestLooper.getNewExecutor(), mockListener); - multiVibratorListener.register(mockVibratorManager); + multiVibratorListener.register(mockVibratorManager, vibratorIds); assertTrue(multiVibratorListener.hasRegisteredListeners()); multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); // none diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 7b96699f7f71..857df1038df1 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -213,6 +213,8 @@ <assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="gpu_service" /> <assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="keystore" /> + <assign-permission name="android.permission.DYNAMIC_INSTRUMENTATION" uid="uprobestats" /> + <split-permission name="android.permission.ACCESS_FINE_LOCATION"> <new-permission name="android.permission.ACCESS_COARSE_LOCATION" /> </split-permission> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 56e55df3f27c..7ced809d2a3a 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -402,6 +402,9 @@ applications that come with the platform <permission name="android.permission.SHOW_CUSTOMIZED_RESOLVER"/> <!-- Permission required for access VIBRATOR_STATE. --> <permission name="android.permission.ACCESS_VIBRATOR_STATE"/> + <!-- Permission required for vendor vibration effects and sessions. --> + <permission name="android.permission.VIBRATE_VENDOR_EFFECTS"/> + <permission name="android.permission.START_VIBRATION_SESSIONS"/> <!-- Permission required for UsageStatsTest CTS test. --> <permission name="android.permission.MANAGE_NOTIFICATIONS"/> <!-- Permission required for CompanionDeviceManager CTS test. --> diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl index f8d3bffbe00b..2b0802b54c14 100644 --- a/data/keyboards/Generic.kl +++ b/data/keyboards/Generic.kl @@ -171,7 +171,7 @@ key 143 WAKEUP # key 149 "KEY_PROG2" key 150 EXPLORER # key 151 "KEY_MSDOS" -key 152 POWER +key 152 LOCK # key 153 "KEY_DIRECTION" # key 154 "KEY_CYCLEWINDOWS" key 155 ENVELOPE @@ -200,20 +200,20 @@ key 177 PAGE_UP key 178 PAGE_DOWN key 179 NUMPAD_LEFT_PAREN key 180 NUMPAD_RIGHT_PAREN -# key 181 "KEY_NEW" +key 181 NEW # key 182 "KEY_REDO" -# key 183 F13 -# key 184 F14 -# key 185 F15 -# key 186 F16 -# key 187 F17 -# key 188 F18 -# key 189 F19 -# key 190 F20 -# key 191 F21 -# key 192 F22 -# key 193 F23 -# key 194 F24 +key 183 F13 +key 184 F14 +key 185 F15 +key 186 F16 +key 187 F17 +key 188 F18 +key 189 F19 +key 190 F20 +key 191 F21 +key 192 F22 +key 193 F23 +key 194 F24 # key 195 (undefined) # key 196 (undefined) # key 197 (undefined) @@ -225,11 +225,11 @@ key 201 MEDIA_PAUSE # key 203 "KEY_PROG4" key 204 NOTIFICATION # key 205 "KEY_SUSPEND" -# key 206 "KEY_CLOSE" +key 206 CLOSE key 207 MEDIA_PLAY key 208 MEDIA_FAST_FORWARD # key 209 "KEY_BASSBOOST" -# key 210 "KEY_PRINT" +key 210 PRINT # key 211 "KEY_HP" key 212 CAMERA key 213 MUSIC @@ -328,7 +328,7 @@ key 368 LANGUAGE_SWITCH # key 369 "KEY_TITLE" key 370 CAPTIONS # key 371 "KEY_ANGLE" -# key 372 "KEY_ZOOM" +key 372 FULLSCREEN # key 373 "KEY_MODE" # key 374 "KEY_KEYBOARD" # key 375 "KEY_SCREEN" @@ -425,12 +425,15 @@ key 582 VOICE_ASSIST # Linux KEY_ASSISTANT key 583 ASSIST key 585 EMOJI_PICKER +key 586 DICTATE key 656 MACRO_1 key 657 MACRO_2 key 658 MACRO_3 key 659 MACRO_4 # Keys defined by HID usages +key usage 0x010082 LOCK FALLBACK_USAGE_MAPPING +key usage 0x01009B DO_NOT_DISTURB FALLBACK_USAGE_MAPPING key usage 0x0c0065 SCREENSHOT FALLBACK_USAGE_MAPPING key usage 0x0c0067 WINDOW FALLBACK_USAGE_MAPPING key usage 0x0c006F BRIGHTNESS_UP FALLBACK_USAGE_MAPPING @@ -438,12 +441,17 @@ key usage 0x0c0070 BRIGHTNESS_DOWN FALLBACK_USAGE_MAPPING key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP FALLBACK_USAGE_MAPPING key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN FALLBACK_USAGE_MAPPING key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE FALLBACK_USAGE_MAPPING +key usage 0x0c00D8 DICTATE FALLBACK_USAGE_MAPPING key usage 0x0c00D9 EMOJI_PICKER FALLBACK_USAGE_MAPPING key usage 0x0c0173 MEDIA_AUDIO_TRACK FALLBACK_USAGE_MAPPING key usage 0x0c019C PROFILE_SWITCH FALLBACK_USAGE_MAPPING key usage 0x0c019F SETTINGS FALLBACK_USAGE_MAPPING key usage 0x0c01A2 ALL_APPS FALLBACK_USAGE_MAPPING +key usage 0x0c0201 NEW FALLBACK_USAGE_MAPPING +key usage 0x0c0203 CLOSE FALLBACK_USAGE_MAPPING +key usage 0x0c0208 PRINT FALLBACK_USAGE_MAPPING key usage 0x0c0227 REFRESH FALLBACK_USAGE_MAPPING +key usage 0x0c0232 FULLSCREEN FALLBACK_USAGE_MAPPING key usage 0x0c029D LANGUAGE_SWITCH FALLBACK_USAGE_MAPPING key usage 0x0c029F RECENT_APPS FALLBACK_USAGE_MAPPING key usage 0x0c02A2 ALL_APPS FALLBACK_USAGE_MAPPING diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 4c47de0ca754..d55a71e21931 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -1761,7 +1761,7 @@ public abstract class ColorSpace { if (Flags.displayBt2020Colorspace()) { sNamedColorSpaceMap.put(Named.DISPLAY_BT2020.ordinal(), new ColorSpace.Rgb( - "BT 2020", + "Display BT. 2020", BT2020_PRIMARIES, ILLUMINANT_D65, null, diff --git a/keystore/java/android/security/keystore/KeyStoreManager.java b/keystore/java/android/security/keystore/KeyStoreManager.java index 197aaba4bcb5..e6091c1da8a5 100644 --- a/keystore/java/android/security/keystore/KeyStoreManager.java +++ b/keystore/java/android/security/keystore/KeyStoreManager.java @@ -49,7 +49,7 @@ import java.util.List; */ @FlaggedApi(android.security.Flags.FLAG_KEYSTORE_GRANT_API) @SystemService(Context.KEYSTORE_SERVICE) -public class KeyStoreManager { +public final class KeyStoreManager { private static final String TAG = "KeyStoreManager"; private static final Object sInstanceLock = new Object(); diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml index 501bedd50f55..c2755ef6ccb6 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml @@ -19,6 +19,7 @@ android:layout_height="wrap_content" android:layout_width="wrap_content" android:orientation="vertical" + android:clipChildren="false" android:id="@+id/bubble_expanded_view"> <com.android.wm.shell.bubbles.bar.BubbleBarHandleView diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml index f1ecde49ce78..7aca921dccc7 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml @@ -14,20 +14,18 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<com.android.wm.shell.bubbles.bar.BubbleBarMenuView - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" +<com.android.wm.shell.bubbles.bar.BubbleBarMenuView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" + android:clipToPadding="false" android:minWidth="@dimen/bubble_bar_manage_menu_min_width" android:orientation="vertical" - android:elevation="@dimen/bubble_manage_menu_elevation" - android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top" - android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding" - android:paddingBottom="@dimen/bubble_bar_manage_menu_padding" - android:clipToPadding="false"> + android:visibility="invisible" + tools:visibility="visible"> <LinearLayout android:id="@+id/bubble_bar_manage_menu_bubble_section" 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 b9a305062b46..c92a2786e49b 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 @@ -21,6 +21,7 @@ import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION; +import static android.window.BackEvent.EDGE_NONE; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; @@ -533,7 +534,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (keyAction == MotionEvent.ACTION_DOWN) { if (!mBackGestureStarted) { - mShouldStartOnNextMoveEvent = true; + if (swipeEdge == EDGE_NONE) { + // start animation immediately for non-gestural sources (without ACTION_MOVE + // events) + mThresholdCrossed = true; + onGestureStarted(touchX, touchY, swipeEdge); + mShouldStartOnNextMoveEvent = false; + } else { + mShouldStartOnNextMoveEvent = true; + } } } else if (keyAction == MotionEvent.ACTION_MOVE) { if (!mBackGestureStarted && mShouldStartOnNextMoveEvent) { @@ -1074,6 +1083,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mCurrentTracker.updateStartLocation(); BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]); dispatchOnBackStarted(mActiveCallback, startEvent); + // TODO(b/373544911): onBackStarted is dispatched here so that + // WindowOnBackInvokedDispatcher knows about the back navigation and intercepts touch + // events while it's active. It would be cleaner and safer to disable multitouch + // altogether (same as in gesture-nav). + dispatchOnBackStarted(mBackNavigationInfo.getOnBackInvokedCallback(), startEvent); } } 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 2a50e4d0d74b..272dfecb0bf9 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 @@ -222,7 +222,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView mHandleView.setAccessibilityDelegate(new HandleViewAccessibilityDelegate()); } - mMenuViewController = new BubbleBarMenuViewController(mContext, this); + mMenuViewController = new BubbleBarMenuViewController(mContext, mHandleView, this); mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() { @Override public void onMenuVisibilityChanged(boolean visible) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java index e781c07f01a7..712e41b0b3c5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java @@ -17,17 +17,18 @@ package com.android.wm.shell.bubbles.bar; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.annotation.Nullable; import android.content.Context; -import android.graphics.Outline; -import android.graphics.Path; -import android.graphics.RectF; +import android.graphics.Canvas; +import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; -import android.view.ViewOutlineProvider; import androidx.annotation.ColorInt; +import androidx.annotation.VisibleForTesting; +import androidx.core.animation.IntProperty; import androidx.core.content.ContextCompat; import com.android.wm.shell.R; @@ -37,14 +38,33 @@ import com.android.wm.shell.R; */ public class BubbleBarHandleView extends View { private static final long COLOR_CHANGE_DURATION = 120; - // Path used to draw the dots - private final Path mPath = new Path(); + /** Custom property to set handle color. */ + private static final IntProperty<BubbleBarHandleView> HANDLE_COLOR = new IntProperty<>( + "handleColor") { + @Override + public void setValue(BubbleBarHandleView bubbleBarHandleView, int color) { + bubbleBarHandleView.setHandleColor(color); + } + + @Override + public Integer get(BubbleBarHandleView bubbleBarHandleView) { + return bubbleBarHandleView.getHandleColor(); + } + }; + + @VisibleForTesting + final Paint mHandlePaint = new Paint(); private final @ColorInt int mHandleLightColor; private final @ColorInt int mHandleDarkColor; - private @ColorInt int mCurrentColor; + private final ArgbEvaluator mArgbEvaluator = ArgbEvaluator.getInstance(); + private final float mHandleHeight; + private final float mHandleWidth; + private float mCurrentHandleHeight; + private float mCurrentHandleWidth; @Nullable private ObjectAnimator mColorChangeAnim; + private @ColorInt int mRegionSamplerColor; public BubbleBarHandleView(Context context) { this(context, null /* attrs */); @@ -61,30 +81,52 @@ public class BubbleBarHandleView extends View { public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - final int handleHeight = getResources().getDimensionPixelSize( + mHandlePaint.setFlags(Paint.ANTI_ALIAS_FLAG); + mHandlePaint.setStyle(Paint.Style.FILL); + mHandlePaint.setColor(0); + mHandleHeight = getResources().getDimensionPixelSize( R.dimen.bubble_bar_expanded_view_handle_height); + mHandleWidth = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_caption_width); mHandleLightColor = ContextCompat.getColor(getContext(), R.color.bubble_bar_expanded_view_handle_light); mHandleDarkColor = ContextCompat.getColor(getContext(), R.color.bubble_bar_expanded_view_handle_dark); - - setClipToOutline(true); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - final int handleCenterY = view.getHeight() / 2; - final int handleTop = handleCenterY - handleHeight / 2; - final int handleBottom = handleTop + handleHeight; - final int radius = handleHeight / 2; - RectF handle = new RectF(/* left = */ 0, handleTop, view.getWidth(), handleBottom); - mPath.reset(); - mPath.addRoundRect(handle, radius, radius, Path.Direction.CW); - outline.setPath(mPath); - } - }); + mCurrentHandleHeight = mHandleHeight; + mCurrentHandleWidth = mHandleWidth; setContentDescription(getResources().getString(R.string.handle_text)); } + private void setHandleColor(int color) { + mHandlePaint.setColor(color); + invalidate(); + } + + private int getHandleColor() { + return mHandlePaint.getColor(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + float handleLeft = (getWidth() - mCurrentHandleWidth) / 2; + float handleRight = handleLeft + mCurrentHandleWidth; + float handleCenterY = (float) getHeight() / 2; + float handleTop = (int) (handleCenterY - mCurrentHandleHeight / 2); + float handleBottom = handleTop + mCurrentHandleHeight; + float cornerRadius = mCurrentHandleHeight / 2; + canvas.drawRoundRect(handleLeft, handleTop, handleRight, handleBottom, cornerRadius, + cornerRadius, mHandlePaint); + } + + /** Sets handle width, height and color. Does not change the layout properties */ + private void setHandleProperties(float width, float height, int color) { + mCurrentHandleHeight = height; + mCurrentHandleWidth = width; + mHandlePaint.setColor(color); + invalidate(); + } + /** * Updates the handle color. * @@ -94,15 +136,15 @@ public class BubbleBarHandleView extends View { */ public void updateHandleColor(boolean isRegionDark, boolean animated) { int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor; - if (newColor == mCurrentColor) { + if (newColor == mRegionSamplerColor) { return; } + mRegionSamplerColor = newColor; if (mColorChangeAnim != null) { mColorChangeAnim.cancel(); } - mCurrentColor = newColor; if (animated) { - mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor); + mColorChangeAnim = ObjectAnimator.ofArgb(this, HANDLE_COLOR, newColor); mColorChangeAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -112,7 +154,39 @@ public class BubbleBarHandleView extends View { mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION); mColorChangeAnim.start(); } else { - setBackgroundColor(newColor); + setHandleColor(newColor); } } + + /** Returns handle padding top. */ + public int getHandlePaddingTop() { + return (getHeight() - getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_handle_height)) / 2; + } + + /** Animates handle for the bubble menu. */ + public void animateHandleForMenu(float progress, float widthDelta, float heightDelta, + int menuColor) { + float currentWidth = mHandleWidth + widthDelta * progress; + float currentHeight = mHandleHeight + heightDelta * progress; + int color = (int) mArgbEvaluator.evaluate(progress, mRegionSamplerColor, menuColor); + setHandleProperties(currentWidth, currentHeight, color); + setTranslationY(heightDelta * progress / 2); + } + + /** Restores all the properties that were animated to the default values. */ + public void restoreAnimationDefaults() { + setHandleProperties(mHandleWidth, mHandleHeight, mRegionSamplerColor); + setTranslationY(0); + } + + /** Returns the handle height. */ + public int getHandleHeight() { + return (int) mHandleHeight; + } + + /** Returns the handle width. */ + public int getHandleWidth() { + return (int) mHandleWidth; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java index 0ee20ef1731f..99e20097e61c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java @@ -47,6 +47,10 @@ public class BubbleBarMenuView extends LinearLayout { private ImageView mBubbleIconView; private ImageView mBubbleDismissIconView; private TextView mBubbleTitleView; + // The animation has three stages. Each stage transition lasts until the animation ends. In + // stage 1, the title item content fades in. In stage 2, the background of the option items + // fades in. In stage 3, the option item content fades in. + private static final int SHOW_MENU_STAGES_COUNT = 3; public BubbleBarMenuView(Context context) { this(context, null /* attrs */); @@ -97,6 +101,35 @@ public class BubbleBarMenuView extends LinearLayout { } } + /** Animates the menu from the specified start scale. */ + public void animateFromStartScale(float currentScale, float progress) { + int menuItemElevation = getResources().getDimensionPixelSize( + R.dimen.bubble_manage_menu_elevation); + setScaleX(currentScale); + setScaleY(currentScale); + setAlphaForTitleViews(progress); + mBubbleSectionView.setElevation(menuItemElevation * progress); + float actionsBackgroundAlpha = Math.max(0, + (progress - (float) 1 / SHOW_MENU_STAGES_COUNT) * (SHOW_MENU_STAGES_COUNT - 1)); + float actionItemsAlpha = Math.max(0, + (progress - (float) 2 / SHOW_MENU_STAGES_COUNT) * SHOW_MENU_STAGES_COUNT); + mActionsSectionView.setAlpha(actionsBackgroundAlpha); + mActionsSectionView.setElevation(menuItemElevation * actionsBackgroundAlpha); + setMenuItemViewsAlpha(actionItemsAlpha); + } + + private void setAlphaForTitleViews(float alpha) { + mBubbleIconView.setAlpha(alpha); + mBubbleTitleView.setAlpha(alpha); + mBubbleDismissIconView.setAlpha(alpha); + } + + private void setMenuItemViewsAlpha(float alpha) { + for (int i = mActionsSectionView.getChildCount() - 1; i >= 0; i--) { + mActionsSectionView.getChildAt(i).setAlpha(alpha); + } + } + /** Update menu details with bubble info */ void updateInfo(Bubble bubble) { if (bubble.getIcon() != null) { @@ -153,6 +186,11 @@ public class BubbleBarMenuView extends LinearLayout { return mBubbleSectionView.getAlpha(); } + /** Return title menu item height. */ + public float getTitleItemHeight() { + return mBubbleSectionView.getHeight(); + } + /** * Menu action details used to create menu items */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java index 514810745e10..9dd0cae20370 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java @@ -15,6 +15,9 @@ */ package com.android.wm.shell.bubbles.bar; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -26,13 +29,10 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import androidx.dynamicanimation.animation.DynamicAnimation; -import androidx.dynamicanimation.animation.SpringForce; - +import com.android.app.animation.Interpolators; import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubble; -import com.android.wm.shell.shared.animation.PhysicsAnimator; import java.util.ArrayList; @@ -40,22 +40,26 @@ import java.util.ArrayList; * Manages bubble bar expanded view menu presentation and animations */ class BubbleBarMenuViewController { - private static final float MENU_INITIAL_SCALE = 0.5f; + + private static final float WIDTH_SWAP_FRACTION = 0.4F; + private static final long MENU_ANIMATION_DURATION = 600; + private final Context mContext; private final ViewGroup mRootView; + private final BubbleBarHandleView mHandleView; private @Nullable Listener mListener; private @Nullable Bubble mBubble; private @Nullable BubbleBarMenuView mMenuView; /** A transparent view used to intercept touches to collapse menu when presented */ private @Nullable View mScrimView; - private @Nullable PhysicsAnimator<BubbleBarMenuView> mMenuAnimator; - private PhysicsAnimator.SpringConfig mMenuSpringConfig; + private @Nullable ValueAnimator mMenuAnimator; + - BubbleBarMenuViewController(Context context, ViewGroup rootView) { + BubbleBarMenuViewController(Context context, BubbleBarHandleView handleView, + ViewGroup rootView) { mContext = context; mRootView = rootView; - mMenuSpringConfig = new PhysicsAnimator.SpringConfig( - SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY); + mHandleView = handleView; } /** Tells if the menu is visible or being animated */ @@ -81,20 +85,21 @@ class BubbleBarMenuViewController { if (mMenuView == null || mScrimView == null) { setupMenu(); } - cancelAnimations(); - mMenuView.setVisibility(View.VISIBLE); - mScrimView.setVisibility(View.VISIBLE); - Runnable endActions = () -> { - mMenuView.getChildAt(0).requestAccessibilityFocus(); - if (mListener != null) { - mListener.onMenuVisibilityChanged(true /* isShown */); + runOnMenuIsMeasured(() -> { + mMenuView.setVisibility(View.VISIBLE); + mScrimView.setVisibility(View.VISIBLE); + Runnable endActions = () -> { + mMenuView.getChildAt(0).requestAccessibilityFocus(); + if (mListener != null) { + mListener.onMenuVisibilityChanged(true /* isShown */); + } + }; + if (animated) { + animateTransition(true /* show */, endActions); + } else { + endActions.run(); } - }; - if (animated) { - animateTransition(true /* show */, endActions); - } else { - endActions.run(); - } + }); } /** @@ -103,18 +108,30 @@ class BubbleBarMenuViewController { */ void hideMenu(boolean animated) { if (mMenuView == null || mScrimView == null) return; - cancelAnimations(); - Runnable endActions = () -> { - mMenuView.setVisibility(View.GONE); - mScrimView.setVisibility(View.GONE); - if (mListener != null) { - mListener.onMenuVisibilityChanged(false /* isShown */); + runOnMenuIsMeasured(() -> { + Runnable endActions = () -> { + mHandleView.restoreAnimationDefaults(); + mMenuView.setVisibility(View.GONE); + mScrimView.setVisibility(View.GONE); + mHandleView.setVisibility(View.VISIBLE); + if (mListener != null) { + mListener.onMenuVisibilityChanged(false /* isShown */); + } + }; + if (animated) { + animateTransition(false /* show */, endActions); + } else { + endActions.run(); } - }; - if (animated) { - animateTransition(false /* show */, endActions); + }); + } + + private void runOnMenuIsMeasured(Runnable action) { + if (mMenuView.getWidth() == 0 || mMenuView.getHeight() == 0) { + // the menu view is not yet measured, postpone showing the animation + mMenuView.post(() -> runOnMenuIsMeasured(action)); } else { - endActions.run(); + action.run(); } } @@ -125,24 +142,63 @@ class BubbleBarMenuViewController { */ private void animateTransition(boolean show, Runnable endActions) { if (mMenuView == null) return; - mMenuAnimator = PhysicsAnimator.getInstance(mMenuView); - mMenuAnimator.setDefaultSpringConfig(mMenuSpringConfig); - mMenuAnimator - .spring(DynamicAnimation.ALPHA, show ? 1f : 0f) - .spring(DynamicAnimation.SCALE_Y, show ? 1f : MENU_INITIAL_SCALE) - .withEndActions(() -> { - mMenuAnimator = null; - endActions.run(); - }) - .start(); + float startValue = show ? 0 : 1; + if (mMenuAnimator != null && mMenuAnimator.isRunning()) { + startValue = (float) mMenuAnimator.getAnimatedValue(); + mMenuAnimator.cancel(); + } + ValueAnimator showMenuAnimation = ValueAnimator.ofFloat(startValue, show ? 1 : 0); + showMenuAnimation.setDuration(MENU_ANIMATION_DURATION); + showMenuAnimation.setInterpolator(Interpolators.EMPHASIZED); + showMenuAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mMenuAnimator = null; + endActions.run(); + } + }); + mMenuAnimator = showMenuAnimation; + setupAnimatorListener(showMenuAnimation); + showMenuAnimation.start(); } - /** Cancel running animations */ - private void cancelAnimations() { - if (mMenuAnimator != null) { - mMenuAnimator.cancel(); - mMenuAnimator = null; + /** Setup listener that orchestrates the animation. */ + private void setupAnimatorListener(ValueAnimator showMenuAnimation) { + // Getting views properties start values + int widthDiff = mMenuView.getWidth() - mHandleView.getHandleWidth(); + int handleHeight = mHandleView.getHandleHeight(); + float targetWidth = mHandleView.getHandleWidth() + widthDiff * WIDTH_SWAP_FRACTION; + float targetHeight = targetWidth * mMenuView.getTitleItemHeight() / mMenuView.getWidth(); + int menuColor; + try (TypedArray ta = mContext.obtainStyledAttributes(new int[]{ + com.android.internal.R.attr.materialColorSurfaceBright, + })) { + menuColor = ta.getColor(0, Color.WHITE); } + // Calculating deltas + float swapScale = targetWidth / mMenuView.getWidth(); + float handleWidthDelta = targetWidth - mHandleView.getHandleWidth(); + float handleHeightDelta = targetHeight - handleHeight; + // Setting update listener that will orchestrate the animation + showMenuAnimation.addUpdateListener(animator -> { + float animationProgress = (float) animator.getAnimatedValue(); + boolean showHandle = animationProgress <= WIDTH_SWAP_FRACTION; + mHandleView.setVisibility(showHandle ? View.VISIBLE : View.GONE); + mMenuView.setVisibility(showHandle ? View.GONE : View.VISIBLE); + if (showHandle) { + float handleAnimationProgress = animationProgress / WIDTH_SWAP_FRACTION; + mHandleView.animateHandleForMenu(handleAnimationProgress, handleWidthDelta, + handleHeightDelta, menuColor); + } else { + mMenuView.setTranslationY(mHandleView.getHandlePaddingTop()); + mMenuView.setPivotY(0); + mMenuView.setPivotX((float) mMenuView.getWidth() / 2); + float menuAnimationProgress = + (animationProgress - WIDTH_SWAP_FRACTION) / (1 - WIDTH_SWAP_FRACTION); + float currentMenuScale = swapScale + (1 - swapScale) * menuAnimationProgress; + mMenuView.animateFromStartScale(currentMenuScale, menuAnimationProgress); + } + }); } /** Sets up and inflate menu views */ @@ -150,9 +206,6 @@ class BubbleBarMenuViewController { // Menu view setup mMenuView = (BubbleBarMenuView) LayoutInflater.from(mContext).inflate( R.layout.bubble_bar_menu_view, mRootView, false); - mMenuView.setAlpha(0f); - mMenuView.setPivotY(0f); - mMenuView.setScaleY(MENU_INITIAL_SCALE); mMenuView.setOnCloseListener(() -> hideMenu(true /* animated */)); if (mBubble != null) { mMenuView.updateInfo(mBubble); 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 cefcb757690f..01c680dc8325 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 @@ -205,11 +205,6 @@ class DesktopMixedTransitionHandler( finishTransaction: SurfaceControl.Transaction, finishCallback: TransitionFinishCallback, ): Boolean { - val launchChange = findDesktopTaskChange(info, pending.launchingTask) - if (launchChange == null) { - logV("No launch Change, returning") - return false - } // Check if there's also an immersive change during this launch. val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask -> findDesktopTaskChange(info, exitingTask) @@ -217,6 +212,13 @@ class DesktopMixedTransitionHandler( val minimizeChange = pending.minimizingTask?.let { minimizingTask -> findDesktopTaskChange(info, minimizingTask) } + val launchChange = findDesktopTaskChange(info, pending.launchingTask) + if (launchChange == null) { + check(minimizeChange == null) + check(immersiveExitChange == null) + logV("No launch Change, returning") + return false + } var subAnimationCount = -1 var combinedWct: WindowContainerTransaction? = null 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 162879c97a16..927fd88fb4ff 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 @@ -1770,9 +1770,13 @@ class DesktopTasksController( transition: IBinder, taskIdToMinimize: Int, ) { - val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) ?: return + val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) desktopTasksLimiter.ifPresent { - it.addPendingMinimizeChange(transition, taskToMinimize.displayId, taskToMinimize.taskId) + it.addPendingMinimizeChange( + transition = transition, + displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY, + taskId = taskIdToMinimize + ) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index b27c428f1693..0154d0455e50 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -45,12 +45,17 @@ public interface Pip { } /** - * Set the callback when {@link PipTaskOrganizer#isInPip()} state is changed. + * Set the callback when isInPip state is changed. * - * @param callback The callback accepts the result of {@link PipTaskOrganizer#isInPip()} - * when it's changed. + * @param callback The callback accepts the state of isInPip when it's changed. */ - default void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {} + default void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {} + + /** + * Remove the callback when isInPip state is changed. + * @param callback The callback accepts the state of isInPip when it's changed. + */ + default void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {} /** * Called when showing Pip menu. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 7f6118689dad..588b88753eb9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -104,6 +104,7 @@ import com.android.wm.shell.sysui.UserChangeListener; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -215,7 +216,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb private boolean mIsKeyguardShowingOrAnimating; - private Consumer<Boolean> mOnIsInPipStateChangedListener; + private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>(); @VisibleForTesting interface PipAnimationListener { @@ -501,11 +502,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb false /* saveRestoreSnapFraction */); }); mPipTransitionState.addOnPipTransitionStateChangedListener((oldState, newState) -> { - if (mOnIsInPipStateChangedListener != null) { - final boolean wasInPip = PipTransitionState.isInPip(oldState); - final boolean nowInPip = PipTransitionState.isInPip(newState); - if (nowInPip != wasInPip) { - mOnIsInPipStateChangedListener.accept(nowInPip); + final boolean wasInPip = PipTransitionState.isInPip(oldState); + final boolean nowInPip = PipTransitionState.isInPip(newState); + if (nowInPip != wasInPip) { + for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) { + listener.accept(nowInPip); } } }); @@ -960,13 +961,19 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx); } - private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) { - mOnIsInPipStateChangedListener = callback; - if (mOnIsInPipStateChangedListener != null) { + private void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { + if (callback != null) { + mOnIsInPipStateChangedListeners.add(callback); callback.accept(mPipTransitionState.isInPip()); } } + private void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { + if (callback != null) { + mOnIsInPipStateChangedListeners.remove(callback); + } + } + private void setShelfHeightLocked(boolean visible, int height) { final int shelfHeight = visible ? height : 0; mPipBoundsState.setShelfVisibility(visible, shelfHeight); @@ -1222,9 +1229,16 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) { + public void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { + mMainExecutor.execute(() -> { + PipController.this.addOnIsInPipStateChangedListener(callback); + }); + } + + @Override + public void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { mMainExecutor.execute(() -> { - PipController.this.setOnIsInPipStateChangedListener(callback); + PipController.this.removeOnIsInPipStateChangedListener(callback); }); } 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 d3f537b8f904..bc0918331168 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 @@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.PictureInPictureParams; import android.content.ComponentName; @@ -66,6 +67,8 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; /** @@ -94,7 +97,7 @@ public class PipController implements ConfigurationChangeListener, private final PipTouchHandler mPipTouchHandler; private final ShellExecutor mMainExecutor; private final PipImpl mImpl; - private Consumer<Boolean> mOnIsInPipStateChangedListener; + 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; @@ -413,13 +416,13 @@ public class PipController implements ConfigurationChangeListener, if (mPipTransitionState.isInSwipePipToHomeTransition()) { mPipTransitionState.resetSwipePipToHomeState(); } - if (mOnIsInPipStateChangedListener != null) { - mOnIsInPipStateChangedListener.accept(true /* inPip */); + for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) { + listener.accept(true /* inPip */); } break; case PipTransitionState.EXITED_PIP: - if (mOnIsInPipStateChangedListener != null) { - mOnIsInPipStateChangedListener.accept(false /* inPip */); + for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) { + listener.accept(false /* inPip */); } break; } @@ -451,13 +454,19 @@ public class PipController implements ConfigurationChangeListener, mPipTransitionState.dump(pw, innerPrefix); } - private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) { - mOnIsInPipStateChangedListener = callback; - if (mOnIsInPipStateChangedListener != null) { + private void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { + if (callback != null) { + mOnIsInPipStateChangedListeners.add(callback); callback.accept(mPipTransitionState.isInPip()); } } + private void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { + if (callback != null) { + mOnIsInPipStateChangedListeners.remove(callback); + } + } + private void setLauncherAppIconSize(int iconSizePx) { mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx); } @@ -473,9 +482,16 @@ public class PipController implements ConfigurationChangeListener, public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {} @Override - public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) { + public void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { + mMainExecutor.execute(() -> { + PipController.this.addOnIsInPipStateChangedListener(callback); + }); + } + + @Override + public void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { mMainExecutor.execute(() -> { - PipController.this.setOnIsInPipStateChangedListener(callback); + PipController.this.removeOnIsInPipStateChangedListener(callback); }); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java index d38b848fbb4d..329a10998f23 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java @@ -16,9 +16,8 @@ package com.android.wm.shell.bubbles.bar; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; -import android.graphics.drawable.ColorDrawable; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -47,10 +46,9 @@ public class BubbleBarHandleViewTest extends ShellTestCase { public void testUpdateHandleColor_lightBg() { mHandleView.updateHandleColor(false /* isRegionDark */, false /* animated */); - assertTrue(mHandleView.getClipToOutline()); - assertTrue(mHandleView.getBackground() instanceof ColorDrawable); - ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground(); - assertEquals(bgDrawable.getColor(), + assertFalse(mHandleView.getClipToOutline()); + int handleColor = mHandleView.mHandlePaint.getColor(); + assertEquals(handleColor, ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_dark)); } @@ -58,10 +56,9 @@ public class BubbleBarHandleViewTest extends ShellTestCase { public void testUpdateHandleColor_darkBg() { mHandleView.updateHandleColor(true /* isRegionDark */, false /* animated */); - assertTrue(mHandleView.getClipToOutline()); - assertTrue(mHandleView.getBackground() instanceof ColorDrawable); - ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground(); - assertEquals(bgDrawable.getColor(), + assertFalse(mHandleView.getClipToOutline()); + int handleColor = mHandleView.mHandlePaint.getColor(); + assertEquals(handleColor, ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light)); } } diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp index c6cee07d1946..5ab5a7a59c2a 100644 --- a/libs/appfunctions/Android.bp +++ b/libs/appfunctions/Android.bp @@ -18,10 +18,10 @@ package { } java_sdk_library { - name: "com.google.android.appfunctions.sidecar", + name: "com.android.extensions.appfunctions", owner: "google", srcs: ["java/**/*.java"], - api_packages: ["com.google.android.appfunctions.sidecar"], + api_packages: ["com.android.extensions.appfunctions"], dex_preopt: { enabled: false, }, @@ -31,9 +31,9 @@ java_sdk_library { } prebuilt_etc { - name: "appfunctions.sidecar.xml", + name: "appfunctions.extension.xml", system_ext_specific: true, sub_dir: "permissions", - src: "appfunctions.sidecar.xml", + src: "appfunctions.extension.xml", filename_from_src: true, } diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index faf84a8ab5ac..f56667df92ca 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -1,9 +1,29 @@ // Signature format: 2.0 -package com.google.android.appfunctions.sidecar { +package com.android.extensions.appfunctions { + + public final class AppFunctionException extends java.lang.Exception { + ctor public AppFunctionException(int, @Nullable String); + ctor public AppFunctionException(int, @Nullable String, @NonNull android.os.Bundle); + method public int getErrorCategory(); + method public int getErrorCode(); + method @Nullable public String getErrorMessage(); + method @NonNull public android.os.Bundle getExtras(); + field public static final int ERROR_APP_UNKNOWN_ERROR = 3000; // 0xbb8 + field public static final int ERROR_CANCELLED = 2001; // 0x7d1 + field public static final int ERROR_CATEGORY_APP = 3; // 0x3 + field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1 + field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2 + 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_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 + } public final class AppFunctionManager { ctor public AppFunctionManager(android.content.Context); - method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>); method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>); @@ -15,7 +35,7 @@ package com.google.android.appfunctions.sidecar { public abstract class AppFunctionService extends android.app.Service { ctor public AppFunctionService(); method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + method @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>); field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE"; field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } @@ -29,33 +49,17 @@ package com.google.android.appfunctions.sidecar { public static final class ExecuteAppFunctionRequest.Builder { ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String); - method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest build(); - method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle); - method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument); + method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest build(); + method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument); } public final class ExecuteAppFunctionResponse { - method public int getErrorCategory(); - method @Nullable public String getErrorMessage(); + ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument); + ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle); method @NonNull public android.os.Bundle getExtras(); - method public int getResultCode(); method @NonNull public android.app.appsearch.GenericDocument getResultDocument(); - method public boolean isSuccess(); - method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle); - method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle); - field public static final int ERROR_CATEGORY_APP = 3; // 0x3 - field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1 - field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2 - field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 - field public static final String PROPERTY_RETURN_VALUE = "returnValue"; - field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8 - field public static final int RESULT_CANCELLED = 2001; // 0x7d1 - field public static final int RESULT_DENIED = 1000; // 0x3e8 - field public static final int RESULT_DISABLED = 1002; // 0x3ea - field public static final int RESULT_FUNCTION_NOT_FOUND = 1003; // 0x3eb - field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9 - field public static final int RESULT_OK = 0; // 0x0 - field public static final int RESULT_SYSTEM_ERROR = 2000; // 0x7d0 + field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; } } diff --git a/libs/appfunctions/appfunctions.sidecar.xml b/libs/appfunctions/appfunctions.extension.xml index bef8b6ec7ce6..dd09cc39d12f 100644 --- a/libs/appfunctions/appfunctions.sidecar.xml +++ b/libs/appfunctions/appfunctions.extension.xml @@ -16,6 +16,6 @@ --> <permissions> <library - name="com.google.android.appfunctions.sidecar" - file="/system_ext/framework/com.google.android.appfunctions.sidecar.jar"/> + name="com.android.extensions.appfunctions" + file="/system_ext/framework/com.android.extensions.appfunctions.jar"/> </permissions>
\ No newline at end of file diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java new file mode 100644 index 000000000000..28c3b3df9b1c --- /dev/null +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.extensions.appfunctions; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Represents an app function related errors. */ +public final class AppFunctionException extends Exception { + /** + * The caller does not have the permission to execute an app function. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_DENIED = 1000; + + /** + * The caller supplied invalid arguments to the execution request. + * + * <p>This error may be considered similar to {@link IllegalArgumentException}. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_INVALID_ARGUMENT = 1001; + + /** + * The caller tried to execute a disabled app function. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_DISABLED = 1002; + + /** + * The caller tried to execute a function that does not exist. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_FUNCTION_NOT_FOUND = 1003; + + /** + * An internal unexpected error coming from the system. + * + * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_SYSTEM_ERROR = 2000; + + /** + * The operation was cancelled. Use this error code to report that a cancellation is done after + * receiving a cancellation signal. + * + * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_CANCELLED = 2001; + + /** + * 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 + * unexpected error is thrown from the bound application. + * + * <p>This error is in the {@link #ERROR_CATEGORY_APP} category. + */ + public static final int ERROR_APP_UNKNOWN_ERROR = 3000; + + /** + * The error category is unknown. + * + * <p>This is the default value for {@link #getErrorCategory}. + */ + public static final int ERROR_CATEGORY_UNKNOWN = 0; + + /** + * The error is caused by the app requesting a function execution. + * + * <p>For example, the caller provided invalid parameters in the execution request e.g. an + * invalid function ID. + * + * <p>Errors in the category fall in the range 1000-1999 inclusive. + */ + public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; + + /** + * The error is caused by an issue in the system. + * + * <p>For example, the AppFunctionService implementation is not found by the system. + * + * <p>Errors in the category fall in the range 2000-2999 inclusive. + */ + public static final int ERROR_CATEGORY_SYSTEM = 2; + + /** + * The error is caused by the app providing the function. + * + * <p>For example, the app crashed when the system is executing the request. + * + * <p>Errors in the category fall in the range 3000-3999 inclusive. + */ + public static final int ERROR_CATEGORY_APP = 3; + + private final int mErrorCode; + @Nullable private final String mErrorMessage; + @NonNull private final Bundle mExtras; + + public AppFunctionException(int errorCode, @Nullable String errorMessage) { + this(errorCode, errorMessage, Bundle.EMPTY); + } + + public AppFunctionException( + int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) { + mErrorCode = errorCode; + mErrorMessage = errorMessage; + mExtras = extras; + } + + /** Returns one of the {@code ERROR} constants. */ + @ErrorCode + public int getErrorCode() { + return mErrorCode; + } + + /** Returns the error message. */ + @Nullable + public String getErrorMessage() { + return mErrorMessage; + } + + /** + * Returns the error category. + * + * <p>This method categorizes errors based on their underlying cause, allowing developers to + * implement targeted error handling and provide more informative error messages to users. It + * maps ranges of error codes to specific error categories. + * + * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the error code does not belong to + * any error category. + * + * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding + * error code ranges. + */ + @ErrorCategory + public int getErrorCategory() { + if (mErrorCode >= 1000 && mErrorCode < 2000) { + return ERROR_CATEGORY_REQUEST_ERROR; + } + if (mErrorCode >= 2000 && mErrorCode < 3000) { + return ERROR_CATEGORY_SYSTEM; + } + if (mErrorCode >= 3000 && mErrorCode < 4000) { + return ERROR_CATEGORY_APP; + } + return ERROR_CATEGORY_UNKNOWN; + } + + @NonNull + public Bundle getExtras() { + return mExtras; + } + + /** + * Error codes. + * + * @hide + */ + @IntDef( + prefix = {"ERROR_"}, + value = { + ERROR_DENIED, + ERROR_APP_UNKNOWN_ERROR, + ERROR_FUNCTION_NOT_FOUND, + ERROR_SYSTEM_ERROR, + ERROR_INVALID_ARGUMENT, + ERROR_DISABLED, + ERROR_CANCELLED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ErrorCode {} + + /** + * Error categories. + * + * @hide + */ + @IntDef( + prefix = {"ERROR_CATEGORY_"}, + value = { + ERROR_CATEGORY_UNKNOWN, + ERROR_CATEGORY_REQUEST_ERROR, + ERROR_CATEGORY_APP, + ERROR_CATEGORY_SYSTEM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ErrorCategory {} +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java index 2075104ff868..9eb66a33fedc 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; import android.Manifest; import android.annotation.CallbackExecutor; @@ -31,7 +31,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.concurrent.Executor; -import java.util.function.Consumer; /** * Provides app functions related functionalities. @@ -115,7 +114,9 @@ public final class AppFunctionManager { @NonNull ExecuteAppFunctionRequest sidecarRequest, @NonNull @CallbackExecutor Executor executor, @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer<ExecuteAppFunctionResponse> callback) { + @NonNull + OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> + callback) { Objects.requireNonNull(sidecarRequest); Objects.requireNonNull(executor); Objects.requireNonNull(callback); @@ -126,10 +127,20 @@ public final class AppFunctionManager { platformRequest, executor, cancellationSignal, - (platformResponse) -> { - callback.accept( - SidecarConverter.getSidecarExecuteAppFunctionResponse( - platformResponse)); + new OutcomeReceiver<>() { + @Override + public void onResult( + android.app.appfunctions.ExecuteAppFunctionResponse result) { + callback.onResult( + SidecarConverter.getSidecarExecuteAppFunctionResponse(result)); + } + + @Override + public void onError( + android.app.appfunctions.AppFunctionException exception) { + callback.onError( + SidecarConverter.getSidecarAppFunctionException(exception)); + } }); } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java index 0dc87e45b7e3..55f579138218 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; -import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE; +import static com.android.extensions.appfunctions.SidecarConverter.getPlatformAppFunctionException; +import static com.android.extensions.appfunctions.SidecarConverter.getPlatformExecuteAppFunctionResponse; import android.annotation.MainThread; import android.annotation.NonNull; @@ -26,9 +27,7 @@ import android.content.Intent; import android.os.Binder; import android.os.CancellationSignal; import android.os.IBinder; -import android.util.Log; - -import java.util.function.Consumer; +import android.os.OutcomeReceiver; /** * Abstract base class to provide app functions to the system. @@ -80,10 +79,18 @@ public abstract class AppFunctionService extends Service { platformRequest), callingPackage, cancellationSignal, - (sidecarResponse) -> { - callback.accept( - SidecarConverter.getPlatformExecuteAppFunctionResponse( - sidecarResponse)); + new OutcomeReceiver<>() { + @Override + public void onResult(ExecuteAppFunctionResponse result) { + callback.onResult( + getPlatformExecuteAppFunctionResponse(result)); + } + + @Override + public void onError(AppFunctionException exception) { + callback.onError( + getPlatformAppFunctionException(exception)); + } }); }); @@ -116,12 +123,14 @@ public abstract class AppFunctionService extends Service { * @param request The function execution request. * @param callingPackage The package name of the app that is requesting the execution. * @param cancellationSignal A signal to cancel the execution. - * @param callback A callback to report back the result. + * @param callback A callback to report back the result or error. */ @MainThread public abstract void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull String callingPackage, @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer<ExecuteAppFunctionResponse> callback); + @NonNull + OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> + callback); } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java index 593c5213dd52..baddc245f0f1 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; import android.annotation.NonNull; import android.app.appsearch.GenericDocument; @@ -91,8 +91,8 @@ public final class ExecuteAppFunctionRequest { * Returns the function parameters. The key is the parameter name, and the value is the * parameter value. * - * <p>The bundle may have missing parameters. Developers are advised to implement defensive - * handling measures. + * <p>The {@link GenericDocument} may have missing parameters. Developers are advised to + * implement defensive handling measures. * * <p>Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be * obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java new file mode 100644 index 000000000000..42c3c033ad38 --- /dev/null +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java @@ -0,0 +1,103 @@ +/* + * 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.extensions.appfunctions; + +import android.annotation.NonNull; +import android.app.appfunctions.AppFunctionManager; +import android.app.appsearch.GenericDocument; +import android.os.Bundle; + +import java.util.Objects; + +/** The response to an app function execution. */ +public final class ExecuteAppFunctionResponse { + /** + * The name of the property that stores the function return value within the {@code + * resultDocument}. + * + * <p>See {@link GenericDocument#getProperty(String)} for more information. + * + * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will + * be empty {@link GenericDocument}. + * + * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will + * return {@code null}. + * + * <p>See {@link #getResultDocument} for more information on extracting the return value. + */ + public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; + + /** + * Returns the return value of the executed function. + * + * <p>The return value is stored in a {@link GenericDocument} with the key {@link + * #PROPERTY_RETURN_VALUE}. + * + * <p>See {@link #getResultDocument} for more information on extracting the return value. + */ + @NonNull private final GenericDocument mResultDocument; + + /** Returns the additional metadata data relevant to this function execution response. */ + @NonNull private final Bundle mExtras; + + /** + * @param resultDocument The return value of the executed function. + */ + public ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument) { + this(resultDocument, Bundle.EMPTY); + } + + /** + * @param resultDocument The return value of the executed function. + * @param extras The additional metadata for this function execution response. + */ + public ExecuteAppFunctionResponse( + @NonNull GenericDocument resultDocument, @NonNull Bundle extras) { + mResultDocument = Objects.requireNonNull(resultDocument); + mExtras = Objects.requireNonNull(extras); + } + + /** + * Returns a generic document containing the return value of the executed function. + * + * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. + * + * <p>Sample code for extracting the return value: + * + * <pre> + * GenericDocument resultDocument = response.getResultDocument(); + * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE); + * if (returnValue != null) { + * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString}, + * // {@link GenericDocument#getPropertyLong} etc. + * // Do something with the returnValue + * } + * </pre> + * + * @see AppFunctionManager on how to determine the expected function return. + */ + @NonNull + public GenericDocument getResultDocument() { + return mResultDocument; + } + + /** Returns the additional metadata for this function execution response. */ + @NonNull + public Bundle getExtras() { + return mExtras; + } +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java index b1b05f79f33f..5e1fc7e684e2 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; import android.annotation.NonNull; @@ -28,46 +28,50 @@ public final class SidecarConverter { private SidecarConverter() {} /** - * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} - * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} + * Converts sidecar's {@link ExecuteAppFunctionRequest} into platform's {@link + * android.app.appfunctions.ExecuteAppFunctionRequest} * * @hide */ @NonNull public static android.app.appfunctions.ExecuteAppFunctionRequest getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) { - return new - android.app.appfunctions.ExecuteAppFunctionRequest.Builder( - request.getTargetPackageName(), - request.getFunctionIdentifier()) + return new android.app.appfunctions.ExecuteAppFunctionRequest.Builder( + request.getTargetPackageName(), request.getFunctionIdentifier()) .setExtras(request.getExtras()) .setParameters(request.getParameters()) .build(); } /** - * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} - * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} + * Converts sidecar's {@link ExecuteAppFunctionResponse} into platform's {@link + * android.app.appfunctions.ExecuteAppFunctionResponse} * * @hide */ @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) { - if (response.isSuccess()) { - return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess( - response.getResultDocument(), response.getExtras()); - } else { - return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure( - response.getResultCode(), - response.getErrorMessage(), - response.getExtras()); - } + return new android.app.appfunctions.ExecuteAppFunctionResponse( + response.getResultDocument(), response.getExtras()); } /** - * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} - * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} + * Converts sidecar's {@link AppFunctionException} into platform's {@link + * android.app.appfunctions.AppFunctionException} + * + * @hide + */ + @NonNull + public static android.app.appfunctions.AppFunctionException + getPlatformAppFunctionException(@NonNull AppFunctionException exception) { + return new android.app.appfunctions.AppFunctionException( + exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras()); + } + + /** + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} into sidecar's + * {@link ExecuteAppFunctionRequest} * * @hide */ @@ -75,30 +79,34 @@ public final class SidecarConverter { public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest( @NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) { return new ExecuteAppFunctionRequest.Builder( - request.getTargetPackageName(), - request.getFunctionIdentifier()) + request.getTargetPackageName(), request.getFunctionIdentifier()) .setExtras(request.getExtras()) .setParameters(request.getParameters()) .build(); } /** - * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} - * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} into + * sidecar's {@link ExecuteAppFunctionResponse} * * @hide */ @NonNull public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse( @NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) { - if (response.isSuccess()) { - return ExecuteAppFunctionResponse.newSuccess( - response.getResultDocument(), response.getExtras()); - } else { - return ExecuteAppFunctionResponse.newFailure( - response.getResultCode(), - response.getErrorMessage(), - response.getExtras()); - } + return new ExecuteAppFunctionResponse(response.getResultDocument(), response.getExtras()); + } + + /** + * Converts platform's {@link android.app.appfunctions.AppFunctionException} into + * sidecar's {@link AppFunctionException} + * + * @hide + */ + @NonNull + public static AppFunctionException getSidecarAppFunctionException( + @NonNull android.app.appfunctions.AppFunctionException exception) { + return new AppFunctionException( + exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras()); } } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java deleted file mode 100644 index 4e88fb025a9d..000000000000 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java +++ /dev/null @@ -1,359 +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.google.android.appfunctions.sidecar; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.appsearch.GenericDocument; -import android.os.Bundle; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -/** - * The response to an app function execution. - * - * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel - * functionality and exposes it here as a sidecar library (avoiding direct dependency on the - * platform API). - */ -public final class ExecuteAppFunctionResponse { - /** - * The name of the property that stores the function return value within the {@code - * resultDocument}. - * - * <p>See {@link GenericDocument#getProperty(String)} for more information. - * - * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will - * be empty {@link GenericDocument}. - * - * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will - * return {@code null}. - * - * <p>See {@link #getResultDocument} for more information on extracting the return value. - */ - public static final String PROPERTY_RETURN_VALUE = "returnValue"; - - /** - * The call was successful. - * - * <p>This result code does not belong in an error category. - */ - public static final int RESULT_OK = 0; - - /** - * The caller does not have the permission to execute an app function. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_DENIED = 1000; - - /** - * The caller supplied invalid arguments to the execution request. - * - * <p>This error may be considered similar to {@link IllegalArgumentException}. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_INVALID_ARGUMENT = 1001; - - /** - * The caller tried to execute a disabled app function. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_DISABLED = 1002; - - /** - * The caller tried to execute a function that does not exist. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_FUNCTION_NOT_FOUND = 1003; - - /** - * An internal unexpected error coming from the system. - * - * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. - */ - public static final int RESULT_SYSTEM_ERROR = 2000; - - /** - * The operation was cancelled. Use this error code to report that a cancellation is done after - * receiving a cancellation signal. - * - * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. - */ - public static final int RESULT_CANCELLED = 2001; - - /** - * 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 - * unexpected error is thrown from the bound application. - * - * <p>This error is in the {@link #ERROR_CATEGORY_APP} category. - */ - public static final int RESULT_APP_UNKNOWN_ERROR = 3000; - - /** - * The error category is unknown. - * - * <p>This is the default value for {@link #getErrorCategory}. - */ - public static final int ERROR_CATEGORY_UNKNOWN = 0; - - /** - * The error is caused by the app requesting a function execution. - * - * <p>For example, the caller provided invalid parameters in the execution request e.g. an - * invalid function ID. - * - * <p>Errors in the category fall in the range 1000-1999 inclusive. - */ - public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; - - /** - * The error is caused by an issue in the system. - * - * <p>For example, the AppFunctionService implementation is not found by the system. - * - * <p>Errors in the category fall in the range 2000-2999 inclusive. - */ - public static final int ERROR_CATEGORY_SYSTEM = 2; - - /** - * The error is caused by the app providing the function. - * - * <p>For example, the app crashed when the system is executing the request. - * - * <p>Errors in the category fall in the range 3000-3999 inclusive. - */ - public static final int ERROR_CATEGORY_APP = 3; - - /** The result code of the app function execution. */ - @ResultCode private final int mResultCode; - - /** - * The error message associated with the result, if any. This is {@code null} if the result code - * is {@link #RESULT_OK}. - */ - @Nullable private final String mErrorMessage; - - /** - * Returns the return value of the executed function. - * - * <p>The return value is stored in a {@link GenericDocument} with the key {@link - * #PROPERTY_RETURN_VALUE}. - * - * <p>See {@link #getResultDocument} for more information on extracting the return value. - */ - @NonNull private final GenericDocument mResultDocument; - - /** Returns the additional metadata data relevant to this function execution response. */ - @NonNull private final Bundle mExtras; - - private ExecuteAppFunctionResponse( - @NonNull GenericDocument resultDocument, - @NonNull Bundle extras, - @ResultCode int resultCode, - @Nullable String errorMessage) { - mResultDocument = Objects.requireNonNull(resultDocument); - mExtras = Objects.requireNonNull(extras); - mResultCode = resultCode; - mErrorMessage = errorMessage; - } - - /** - * Returns result codes from throwable. - * - * @hide - */ - static @ResultCode int getResultCode(@NonNull Throwable t) { - if (t instanceof IllegalArgumentException) { - return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT; - } - return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR; - } - - /** - * Returns a successful response. - * - * @param resultDocument The return value of the executed function. - * @param extras The additional metadata data relevant to this function execution response. - */ - @NonNull - public static ExecuteAppFunctionResponse newSuccess( - @NonNull GenericDocument resultDocument, @Nullable Bundle extras) { - Objects.requireNonNull(resultDocument); - Bundle actualExtras = getActualExtras(extras); - - return new ExecuteAppFunctionResponse( - resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null); - } - - /** - * Returns a failure response. - * - * @param resultCode The result code of the app function execution. - * @param extras The additional metadata data relevant to this function execution response. - * @param errorMessage The error message associated with the result, if any. - */ - @NonNull - public static ExecuteAppFunctionResponse newFailure( - @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) { - if (resultCode == RESULT_OK) { - throw new IllegalArgumentException("resultCode must not be RESULT_OK"); - } - Bundle actualExtras = getActualExtras(extras); - GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build(); - return new ExecuteAppFunctionResponse( - emptyDocument, actualExtras, resultCode, errorMessage); - } - - private static Bundle getActualExtras(@Nullable Bundle extras) { - if (extras == null) { - return Bundle.EMPTY; - } - return extras; - } - - /** - * Returns the error category of the {@link ExecuteAppFunctionResponse}. - * - * <p>This method categorizes errors based on their underlying cause, allowing developers to - * implement targeted error handling and provide more informative error messages to users. It - * maps ranges of result codes to specific error categories. - * - * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to - * ensure correct categorization of the failed response. - * - * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to - * any error category, for example, in the case of a successful result with {@link #RESULT_OK}. - * - * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding - * result code ranges. - */ - @ErrorCategory - public int getErrorCategory() { - if (mResultCode >= 1000 && mResultCode < 2000) { - return ERROR_CATEGORY_REQUEST_ERROR; - } - if (mResultCode >= 2000 && mResultCode < 3000) { - return ERROR_CATEGORY_SYSTEM; - } - if (mResultCode >= 3000 && mResultCode < 4000) { - return ERROR_CATEGORY_APP; - } - return ERROR_CATEGORY_UNKNOWN; - } - - /** - * Returns a generic document containing the return value of the executed function. - * - * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. - * - * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed - * function does not produce a return value. - * - * <p>Sample code for extracting the return value: - * - * <pre> - * GenericDocument resultDocument = response.getResultDocument(); - * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE); - * if (returnValue != null) { - * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString}, - * // {@link GenericDocument#getPropertyLong} etc. - * // Do something with the returnValue - * } - * </pre> - */ - @NonNull - public GenericDocument getResultDocument() { - return mResultDocument; - } - - /** Returns the extras of the app function execution. */ - @NonNull - public Bundle getExtras() { - return mExtras; - } - - /** - * Returns {@code true} if {@link #getResultCode} equals {@link - * ExecuteAppFunctionResponse#RESULT_OK}. - */ - public boolean isSuccess() { - return getResultCode() == RESULT_OK; - } - - /** - * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}. - */ - @ResultCode - public int getResultCode() { - return mResultCode; - } - - /** - * Returns the error message associated with this result. - * - * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. - */ - @Nullable - public String getErrorMessage() { - return mErrorMessage; - } - - /** - * Result codes. - * - * @hide - */ - @IntDef( - prefix = {"RESULT_"}, - value = { - RESULT_OK, - RESULT_DENIED, - RESULT_APP_UNKNOWN_ERROR, - RESULT_SYSTEM_ERROR, - RESULT_FUNCTION_NOT_FOUND, - RESULT_INVALID_ARGUMENT, - RESULT_DISABLED, - RESULT_CANCELLED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ResultCode {} - - /** - * Error categories. - * - * @hide - */ - @IntDef( - prefix = {"ERROR_CATEGORY_"}, - value = { - ERROR_CATEGORY_UNKNOWN, - ERROR_CATEGORY_REQUEST_ERROR, - ERROR_CATEGORY_APP, - ERROR_CATEGORY_SYSTEM - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ErrorCategory {} -} diff --git a/libs/appfunctions/tests/Android.bp b/libs/appfunctions/tests/Android.bp index 6f5eff305d8d..db79675ae9f7 100644 --- a/libs/appfunctions/tests/Android.bp +++ b/libs/appfunctions/tests/Android.bp @@ -25,7 +25,7 @@ android_test { "androidx.test.rules", "androidx.test.ext.junit", "androidx.core_core-ktx", - "com.google.android.appfunctions.sidecar.impl", + "com.android.extensions.appfunctions.impl", "junit", "kotlin-test", "mockito-target-extended-minus-junit4", diff --git a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt index 264f84209caf..11202d58e484 100644 --- a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt +++ b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt @@ -14,14 +14,15 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar.tests +package com.android.extensions.appfunctions.tests +import android.app.appfunctions.AppFunctionException import android.app.appfunctions.ExecuteAppFunctionRequest import android.app.appfunctions.ExecuteAppFunctionResponse import android.app.appsearch.GenericDocument import android.os.Bundle import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.android.appfunctions.sidecar.SidecarConverter +import com.android.extensions.appfunctions.SidecarConverter import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -60,7 +61,7 @@ class SidecarConverterTest { .setPropertyLong("testLong", 23) .build() val sidecarRequest = - com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder( + com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder( "targetPkg", "targetFunctionId" ) @@ -83,44 +84,38 @@ class SidecarConverterTest { GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "") .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true) .build() - val platformResponse = ExecuteAppFunctionResponse.newSuccess(resultGd, null) + val platformResponse = ExecuteAppFunctionResponse(resultGd) val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse( platformResponse ) - assertThat(sidecarResponse.isSuccess).isTrue() assertThat( sidecarResponse.resultDocument.getProperty( ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE ) ) .isEqualTo(booleanArrayOf(true)) - assertThat(sidecarResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK) - assertThat(sidecarResponse.errorMessage).isNull() } @Test - fun getSidecarExecuteAppFunctionResponse_errorResponse_sameContents() { - val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build() - val platformResponse = - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, - null, - null + fun getSidecarAppFunctionException_sameContents() { + val bundle = Bundle() + bundle.putString("key", "value") + val platformException = + AppFunctionException( + AppFunctionException.ERROR_SYSTEM_ERROR, + "error", + bundle ) - val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse( - platformResponse + val sidecarException = SidecarConverter.getSidecarAppFunctionException( + platformException ) - assertThat(sidecarResponse.isSuccess).isFalse() - assertThat(sidecarResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace) - assertThat(sidecarResponse.resultDocument.id).isEqualTo(emptyGd.id) - assertThat(sidecarResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType) - assertThat(sidecarResponse.resultCode) - .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR) - assertThat(sidecarResponse.errorMessage).isNull() + assertThat(sidecarException.errorCode).isEqualTo(AppFunctionException.ERROR_SYSTEM_ERROR) + assertThat(sidecarException.errorMessage).isEqualTo("error") + assertThat(sidecarException.extras.getString("key")).isEqualTo("value") } @Test @@ -129,44 +124,39 @@ class SidecarConverterTest { GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "") .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true) .build() - val sidecarResponse = com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse - .newSuccess(resultGd, null) + val sidecarResponse = + com.android.extensions.appfunctions.ExecuteAppFunctionResponse(resultGd) val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse( sidecarResponse ) - assertThat(platformResponse.isSuccess).isTrue() assertThat( platformResponse.resultDocument.getProperty( ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE ) ) .isEqualTo(booleanArrayOf(true)) - assertThat(platformResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK) - assertThat(platformResponse.errorMessage).isNull() } @Test - fun getPlatformExecuteAppFunctionResponse_errorResponse_sameContents() { - val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build() - val sidecarResponse = - com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, - null, - null + fun getPlatformAppFunctionException_sameContents() { + val bundle = Bundle() + bundle.putString("key", "value") + val sidecarException = + com.android.extensions.appfunctions.AppFunctionException( + AppFunctionException.ERROR_SYSTEM_ERROR, + "error", + bundle ) - val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse( - sidecarResponse + val platformException = SidecarConverter.getPlatformAppFunctionException( + sidecarException ) - assertThat(platformResponse.isSuccess).isFalse() - assertThat(platformResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace) - assertThat(platformResponse.resultDocument.id).isEqualTo(emptyGd.id) - assertThat(platformResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType) - assertThat(platformResponse.resultCode) - .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR) - assertThat(platformResponse.errorMessage).isNull() + assertThat(platformException.errorCode) + .isEqualTo(AppFunctionException.ERROR_SYSTEM_ERROR) + assertThat(platformException.errorMessage).isEqualTo("error") + assertThat(platformException.extras.getString("key")).isEqualTo("value") } } diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index b08a86ee8f46..bd65b2ecb76a 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -17,6 +17,7 @@ package android.media; import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC; +import static android.media.codec.Flags.FLAG_NUM_INPUT_SLOTS; import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST; import static android.media.codec.Flags.FLAG_APV_SUPPORT; @@ -1777,6 +1778,17 @@ public final class MediaFormat { public static final String KEY_SECURITY_MODEL = "security-model"; /** + * A key describing the number of slots used in the codec. When present in input format, + * the associated value indicates the number of input slots. The entry is set by the codec + * if configured with (@link MediaCodec#CONFIGURE_FLAG_BLOCK_MODEL), and will be ignored if set + * by the application. + * <p> + * The associated value is an integer. + */ + @FlaggedApi(FLAG_NUM_INPUT_SLOTS) + public static final String KEY_NUM_SLOTS = "num-slots"; + + /** * QpOffsetRect constitutes the metadata required for encoding a region of interest in an * image or a video frame. The region of interest is represented by a rectangle. The four * integer coordinates of the rectangle are stored in fields left, top, right, bottom. diff --git a/media/java/android/media/audio/common/AidlConversion.java b/media/java/android/media/audio/common/AidlConversion.java index c1d73f9033cf..8521d1c472a8 100644 --- a/media/java/android/media/audio/common/AidlConversion.java +++ b/media/java/android/media/audio/common/AidlConversion.java @@ -705,6 +705,10 @@ public class AidlConversion { aidl.type = AudioDeviceType.OUT_BROADCAST; aidl.connection = AudioDeviceDescription.CONNECTION_BT_LE; break; + case AudioSystem.DEVICE_OUT_MULTICHANNEL_GROUP: + aidl.type = AudioDeviceType.OUT_MULTICHANNEL_GROUP; + aidl.connection = AudioDeviceDescription.CONNECTION_VIRTUAL; + break; case AudioSystem.DEVICE_IN_BUILTIN_MIC: aidl.type = AudioDeviceType.IN_MICROPHONE; break; diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index 90b4aba690d0..52a21e241ba8 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -79,7 +79,7 @@ flag { flag { name: "update_client_profile_priority" - namespace: "media" + namespace: "media_solutions" description : "Feature flag to add updateResourcePriority api to MediaCas" bug: "300565729" } diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java index 391eb225bcab..bb782bf1aee4 100644 --- a/media/java/android/media/quality/AmbientBacklightSettings.java +++ b/media/java/android/media/quality/AmbientBacklightSettings.java @@ -26,6 +26,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** + * Settings for ambient backlight. * @hide */ public class AmbientBacklightSettings implements Parcelable { diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl index e6c79dd9681f..250d59b7c2d7 100644 --- a/media/java/android/media/quality/IMediaQualityManager.aidl +++ b/media/java/android/media/quality/IMediaQualityManager.aidl @@ -30,20 +30,22 @@ import android.media.quality.SoundProfile; */ interface IMediaQualityManager { PictureProfile createPictureProfile(in PictureProfile pp); - void updatePictureProfile(in long id, in PictureProfile pp); - void removePictureProfile(in long id); - PictureProfile getPictureProfileById(in long id); + void updatePictureProfile(in String id, in PictureProfile pp); + void removePictureProfile(in String id); + PictureProfile getPictureProfile(in int type, in String name); List<PictureProfile> getPictureProfilesByPackage(in String packageName); List<PictureProfile> getAvailablePictureProfiles(); - List<PictureProfile> getAllPictureProfiles(); + List<String> getPictureProfilePackageNames(); + List<String> getPictureProfileAllowList(); + void setPictureProfileAllowList(in List<String> packages); SoundProfile createSoundProfile(in SoundProfile pp); - void updateSoundProfile(in long id, in SoundProfile pp); - void removeSoundProfile(in long id); - SoundProfile getSoundProfileById(in long id); + void updateSoundProfile(in String id, in SoundProfile pp); + void removeSoundProfile(in String id); + SoundProfile getSoundProfileById(in String id); List<SoundProfile> getSoundProfilesByPackage(in String packageName); List<SoundProfile> getAvailableSoundProfiles(); - List<SoundProfile> getAllSoundProfiles(); + List<String> getSoundProfilePackageNames(); void registerPictureProfileCallback(in IPictureProfileCallback cb); void registerSoundProfileCallback(in ISoundProfileCallback cb); diff --git a/media/java/android/media/quality/IPictureProfileCallback.aidl b/media/java/android/media/quality/IPictureProfileCallback.aidl index 05441cde31e7..34aa2b061caf 100644 --- a/media/java/android/media/quality/IPictureProfileCallback.aidl +++ b/media/java/android/media/quality/IPictureProfileCallback.aidl @@ -17,6 +17,7 @@ package android.media.quality; +import android.media.quality.ParamCapability; import android.media.quality.PictureProfile; /** @@ -24,7 +25,9 @@ import android.media.quality.PictureProfile; * @hide */ oneway interface IPictureProfileCallback { - void onPictureProfileAdded(in long id, in PictureProfile p); - void onPictureProfileUpdated(in long id, in PictureProfile p); - void onPictureProfileRemoved(in long id, in PictureProfile p); + void onPictureProfileAdded(in String id, in PictureProfile p); + 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); } diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index 38a2025535f4..26d83aca3e7b 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -19,6 +19,7 @@ package android.media.quality; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemService; import android.content.Context; import android.media.tv.flags.Flags; @@ -63,7 +64,7 @@ public final class MediaQualityManager { mService = service; IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() { @Override - public void onPictureProfileAdded(long profileId, PictureProfile profile) { + public void onPictureProfileAdded(String profileId, PictureProfile profile) { synchronized (mLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { // TODO: filter callback record @@ -72,7 +73,7 @@ public final class MediaQualityManager { } } @Override - public void onPictureProfileUpdated(long profileId, PictureProfile profile) { + public void onPictureProfileUpdated(String profileId, PictureProfile profile) { synchronized (mLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { // TODO: filter callback record @@ -81,7 +82,7 @@ public final class MediaQualityManager { } } @Override - public void onPictureProfileRemoved(long profileId, PictureProfile profile) { + public void onPictureProfileRemoved(String profileId, PictureProfile profile) { synchronized (mLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { // TODO: filter callback record @@ -89,6 +90,24 @@ public final class MediaQualityManager { } } } + @Override + public void onParamCapabilitiesChanged(String profileId, List<ParamCapability> caps) { + synchronized (mLock) { + for (PictureProfileCallbackRecord record : mPpCallbackRecords) { + // TODO: filter callback record + record.postParamCapabilitiesChanged(profileId, caps); + } + } + } + @Override + public void onError(int err) { + synchronized (mLock) { + for (PictureProfileCallbackRecord record : mPpCallbackRecords) { + // TODO: filter callback record + record.postError(err); + } + } + } }; ISoundProfileCallback spCallback = new ISoundProfileCallback.Stub() { @Override @@ -175,14 +194,17 @@ public final class MediaQualityManager { /** - * Gets picture profile by given profile ID. - * @return the corresponding picture profile if available; {@code null} if the ID doesn't - * exist or the profile is not accessible to the caller. + * Gets picture profile by given profile type and name. + * + * @return the corresponding picture profile if available; {@code null} if the name doesn't + * exist. * @hide */ - public PictureProfile getPictureProfileById(long profileId) { + @Nullable + public PictureProfile getPictureProfile( + @PictureProfile.ProfileType int type, @NonNull String name) { try { - return mService.getPictureProfileById(profileId); + return mService.getPictureProfile(type, name); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -190,11 +212,13 @@ public final class MediaQualityManager { /** - * @SystemApi gets profiles that available to the given package - * @hide + * Gets profiles that available to the given package. + * + * @hide @SystemApi */ + @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) - public List<PictureProfile> getPictureProfilesByPackage(String packageName) { + public List<PictureProfile> getPictureProfilesByPackage(@NonNull String packageName) { try { return mService.getPictureProfilesByPackage(packageName); } catch (RemoteException e) { @@ -215,13 +239,16 @@ public final class MediaQualityManager { } /** - * @SystemApi all stored picture profiles of all packages - * @hide + * Gets all package names whose picture profiles are available. + * + * @see #getPictureProfilesByPackage(String) + * @hide @SystemApi */ + @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) - public List<PictureProfile> getAllPictureProfiles() { + public List<String> getPictureProfilePackageNames() { try { - return mService.getAllPictureProfiles(); + return mService.getPictureProfilePackageNames(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -231,10 +258,12 @@ public final class MediaQualityManager { /** * Creates a picture profile and store it in the system. * - * @return the stored profile with an assigned profile ID. + * @return the stored profile with an assigned profile ID. {@code null} if it's not created + * successfully. * @hide */ - public PictureProfile createPictureProfile(PictureProfile pp) { + @Nullable + public PictureProfile createPictureProfile(@NonNull PictureProfile pp) { try { return mService.createPictureProfile(pp); } catch (RemoteException e) { @@ -247,7 +276,7 @@ public final class MediaQualityManager { * Updates an existing picture profile and store it in the system. * @hide */ - public void updatePictureProfile(long profileId, PictureProfile pp) { + public void updatePictureProfile(@NonNull String profileId, @NonNull PictureProfile pp) { try { mService.updatePictureProfile(profileId, pp); } catch (RemoteException e) { @@ -260,7 +289,7 @@ public final class MediaQualityManager { * Removes a picture profile from the system. * @hide */ - public void removePictureProfile(long profileId) { + public void removePictureProfile(@NonNull String profileId) { try { mService.removePictureProfile(profileId); } catch (RemoteException e) { @@ -307,7 +336,7 @@ public final class MediaQualityManager { * exist or the profile is not accessible to the caller. * @hide */ - public SoundProfile getSoundProfileById(long profileId) { + public SoundProfile getSoundProfileById(String profileId) { try { return mService.getSoundProfileById(profileId); } catch (RemoteException e) { @@ -346,9 +375,9 @@ public final class MediaQualityManager { * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) - public List<SoundProfile> getAllSoundProfiles() { + public List<String> getSoundProfilePackageNames() { try { - return mService.getAllSoundProfiles(); + return mService.getSoundProfilePackageNames(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -374,7 +403,7 @@ public final class MediaQualityManager { * Updates an existing sound profile and store it in the system. * @hide */ - public void updateSoundProfile(long profileId, SoundProfile sp) { + public void updateSoundProfile(String profileId, SoundProfile sp) { try { mService.updateSoundProfile(profileId, sp); } catch (RemoteException e) { @@ -387,7 +416,7 @@ public final class MediaQualityManager { * Removes a sound profile from the system. * @hide */ - public void removeSoundProfile(long profileId) { + public void removeSoundProfile(String profileId) { try { mService.removeSoundProfile(profileId); } catch (RemoteException e) { @@ -399,7 +428,8 @@ public final class MediaQualityManager { * Gets capability information of the given parameters. * @hide */ - public List<ParamCapability> getParamCapabilities(List<String> names) { + @NonNull + public List<ParamCapability> getParamCapabilities(@NonNull List<String> names) { try { return mService.getParamCapabilities(names); } catch (RemoteException e) { @@ -408,7 +438,38 @@ public final class MediaQualityManager { } /** + * Gets the allowlist of packages that can create and removed picture profiles + * + * @see #createPictureProfile(PictureProfile) + * @see #removePictureProfile(String) + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) + @NonNull + public List<String> getPictureProfileAllowList() { + try { + return mService.getPictureProfileAllowList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets the allowlist of packages that can create and removed picture profiles + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) + public void setPictureProfileAllowList(@NonNull List<String> packageNames) { + try { + mService.setPictureProfileAllowList(packageNames); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns {@code true} if media quality HAL is implemented; {@code false} otherwise. + * @hide */ public boolean isSupported() { try { @@ -581,7 +642,7 @@ public final class MediaQualityManager { return mCallback; } - public void postPictureProfileAdded(final long id, PictureProfile profile) { + public void postPictureProfileAdded(final String id, PictureProfile profile) { mExecutor.execute(new Runnable() { @Override @@ -591,7 +652,7 @@ public final class MediaQualityManager { }); } - public void postPictureProfileUpdated(final long id, PictureProfile profile) { + public void postPictureProfileUpdated(final String id, PictureProfile profile) { mExecutor.execute(new Runnable() { @Override public void run() { @@ -600,7 +661,7 @@ public final class MediaQualityManager { }); } - public void postPictureProfileRemoved(final long id, PictureProfile profile) { + public void postPictureProfileRemoved(final String id, PictureProfile profile) { mExecutor.execute(new Runnable() { @Override public void run() { @@ -608,6 +669,24 @@ public final class MediaQualityManager { } }); } + + public void postParamCapabilitiesChanged(final String id, List<ParamCapability> caps) { + mExecutor.execute(new Runnable() { + @Override + public void run() { + mCallback.onParamCapabilitiesChanged(id, caps); + } + }); + } + + public void postError(int error) { + mExecutor.execute(new Runnable() { + @Override + public void run() { + mCallback.onError(error); + } + }); + } } private static final class SoundProfileCallbackRecord { @@ -681,24 +760,57 @@ public final class MediaQualityManager { */ public abstract static class PictureProfileCallback { /** + * This is invoked when a picture profile has been added. + * + * @param profileId the ID of the profile. + * @param profile the newly added profile. * @hide */ - public void onPictureProfileAdded(long id, PictureProfile profile) { + public void onPictureProfileAdded( + @NonNull String profileId, @NonNull PictureProfile profile) { } + /** + * This is invoked when a picture profile has been updated. + * + * @param profileId the ID of the profile. + * @param profile the profile with updated info. * @hide */ - public void onPictureProfileUpdated(long id, PictureProfile profile) { + public void onPictureProfileUpdated( + @NonNull String profileId, @NonNull PictureProfile profile) { } + /** + * This is invoked when a picture profile has been removed. + * + * @param profileId the ID of the profile. + * @param profile the removed profile. * @hide */ - public void onPictureProfileRemoved(long id, PictureProfile profile) { + public void onPictureProfileRemoved( + @NonNull String profileId, @NonNull PictureProfile profile) { } + /** + * This is invoked when an issue has occurred. + * + * @param errorCode the error code * @hide */ - public void onError(int errorCode) { + public void onError(@PictureProfile.ErrorCode int errorCode) { + } + + /** + * This is invoked when parameter capabilities has been changed due to status changes of the + * content. + * + * @param profileId the ID of the profile used by the media content. + * @param updatedCaps the updated capabilities. + * @hide + */ + public void onParamCapabilitiesChanged( + @NonNull String profileId, @NonNull List<ParamCapability> updatedCaps) { } } diff --git a/media/java/android/media/quality/ParamCapability.java b/media/java/android/media/quality/ParamCapability.java index 70e85920c12f..0b698a9c1ad2 100644 --- a/media/java/android/media/quality/ParamCapability.java +++ b/media/java/android/media/quality/ParamCapability.java @@ -34,7 +34,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) -public class ParamCapability implements Parcelable { +public final class ParamCapability implements Parcelable { /** @hide */ @IntDef(flag = true, prefix = { "TYPE_" }, value = { @@ -104,6 +104,7 @@ public class ParamCapability implements Parcelable { @NonNull private final Bundle mCaps; + /** @hide */ protected ParamCapability(Parcel in) { mName = in.readString(); mIsSupported = in.readBoolean(); @@ -112,7 +113,7 @@ public class ParamCapability implements Parcelable { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mName); dest.writeBoolean(mIsSupported); dest.writeInt(mType); @@ -124,6 +125,7 @@ public class ParamCapability implements Parcelable { return 0; } + @NonNull public static final Creator<ParamCapability> CREATOR = new Creator<ParamCapability>() { @Override public ParamCapability createFromParcel(Parcel in) { diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java index 8fb57124d33e..2be47dd87ef2 100644 --- a/media/java/android/media/quality/PictureProfile.java +++ b/media/java/android/media/quality/PictureProfile.java @@ -71,6 +71,53 @@ public final class PictureProfile implements Parcelable { */ public static final int TYPE_APPLICATION = 2; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, prefix = "ERROR_", value = { + ERROR_UNKNOWN, + ERROR_NO_PERMISSION, + ERROR_DUPLICATE, + ERROR_INVALID_ARGUMENT, + ERROR_NOT_ALLOWLISTED + }) + public @interface ErrorCode {} + + /** + * Error code for unknown errors. + * @hide + */ + public static final int ERROR_UNKNOWN = 0; + + /** + * Error code for missing necessary permission to handle the profiles. + * @hide + */ + public static final int ERROR_NO_PERMISSION = 1; + + /** + * Error code for creating a profile with existing profile type and name. + * + * @see #getProfileType() + * @see #getName() + * @hide + */ + public static final int ERROR_DUPLICATE = 2; + + /** + * Error code for invalid argument. + * @hide + */ + public static final int ERROR_INVALID_ARGUMENT = 3; + + /** + * Error code for the case when an operation requires an allowlist but the caller is not in the + * list. + * + * @see MediaQualityManager#getPictureProfileAllowList() + * @hide + */ + public static final int ERROR_NOT_ALLOWLISTED = 4; + private PictureProfile(@NonNull Parcel in) { mId = in.readString(); diff --git a/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java b/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java index 09573909c288..d9a1221e529c 100644 --- a/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java +++ b/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java @@ -18,6 +18,7 @@ package android.media.audio.common; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -504,6 +505,27 @@ public final class AidlConversionUnitTests { assertEquals(AudioDeviceType.OUT_DEVICE, port.ext.getDevice().device.type.type); } + @Test + public void testAudioDeviceDescriptionConversion() { + for (int nativeDeviceType : AudioSystem.DEVICE_OUT_ALL_SET) { + assertNotEquals( + AidlConversion.api2aidl_NativeType_AudioDeviceDescription(nativeDeviceType) + .type, + AudioDeviceType.NONE); + } + + for (int nativeDeviceType : AudioSystem.DEVICE_IN_ALL_SET) { + if (nativeDeviceType == AudioSystem.DEVICE_IN_COMMUNICATION + || nativeDeviceType == AudioSystem.DEVICE_IN_AMBIENT) { + continue; + } + assertNotEquals( + AidlConversion.api2aidl_NativeType_AudioDeviceDescription(nativeDeviceType) + .type, + AudioDeviceType.NONE); + } + } + private static AudioFormatDescription createPcm16FormatAidl() { final AudioFormatDescription aidl = new AudioFormatDescription(); aidl.type = AudioFormatType.PCM; diff --git a/native/android/Android.bp b/native/android/Android.bp index 3eb99c3387f7..da29c49f9d7b 100644 --- a/native/android/Android.bp +++ b/native/android/Android.bp @@ -55,6 +55,7 @@ cc_library_shared { "surface_control_input_receiver.cpp", "choreographer.cpp", "configuration.cpp", + "dynamic_instrumentation_manager.cpp", "hardware_buffer_jni.cpp", "input.cpp", "input_transfer_token.cpp", @@ -100,6 +101,7 @@ cc_library_shared { "android.hardware.configstore@1.0", "android.hardware.configstore-utils", "android.os.flags-aconfig-cc", + "dynamic_instrumentation_manager_aidl-cpp", "libnativedisplay", "libfmq", ], diff --git a/native/android/dynamic_instrumentation_manager.cpp b/native/android/dynamic_instrumentation_manager.cpp new file mode 100644 index 000000000000..d9bacb116f96 --- /dev/null +++ b/native/android/dynamic_instrumentation_manager.cpp @@ -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. + */ + +#define LOG_TAG "ADynamicInstrumentationManager" +#include <android/dynamic_instrumentation_manager.h> +#include <android/os/instrumentation/ExecutableMethodFileOffsets.h> +#include <android/os/instrumentation/IDynamicInstrumentationManager.h> +#include <android/os/instrumentation/MethodDescriptor.h> +#include <android/os/instrumentation/TargetProcess.h> +#include <binder/Binder.h> +#include <binder/IServiceManager.h> +#include <utils/Log.h> + +#include <mutex> +#include <optional> +#include <string> +#include <vector> + +namespace android::dynamicinstrumentationmanager { + +// Global instance of IDynamicInstrumentationManager, service is obtained only on first use. +static std::mutex mLock; +static sp<os::instrumentation::IDynamicInstrumentationManager> mService; + +sp<os::instrumentation::IDynamicInstrumentationManager> getService() { + std::lock_guard<std::mutex> scoped_lock(mLock); + if (mService == nullptr || !IInterface::asBinder(mService)->isBinderAlive()) { + sp<IBinder> binder = + defaultServiceManager()->waitForService(String16("dynamic_instrumentation")); + mService = interface_cast<os::instrumentation::IDynamicInstrumentationManager>(binder); + } + return mService; +} + +} // namespace android::dynamicinstrumentationmanager + +using namespace android; +using namespace dynamicinstrumentationmanager; + +struct ADynamicInstrumentationManager_TargetProcess { + uid_t uid; + uid_t pid; + std::string processName; + + ADynamicInstrumentationManager_TargetProcess(uid_t uid, pid_t pid, const char* processName) + : uid(uid), pid(pid), processName(processName) {} +}; + +ADynamicInstrumentationManager_TargetProcess* ADynamicInstrumentationManager_TargetProcess_create( + uid_t uid, pid_t pid, const char* processName) { + return new ADynamicInstrumentationManager_TargetProcess(uid, pid, processName); +} + +void ADynamicInstrumentationManager_TargetProcess_destroy( + ADynamicInstrumentationManager_TargetProcess* instance) { + delete instance; +} + +struct ADynamicInstrumentationManager_MethodDescriptor { + std::string fqcn; + std::string methodName; + std::vector<std::string> fqParameters; + + ADynamicInstrumentationManager_MethodDescriptor(const char* fqcn, const char* methodName, + const char* fullyQualifiedParameters[], + size_t numParameters) + : fqcn(fqcn), methodName(methodName) { + std::vector<std::string> fqParameters; + fqParameters.reserve(numParameters); + std::copy_n(fullyQualifiedParameters, numParameters, std::back_inserter(fqParameters)); + this->fqParameters = std::move(fqParameters); + } +}; + +ADynamicInstrumentationManager_MethodDescriptor* +ADynamicInstrumentationManager_MethodDescriptor_create(const char* fullyQualifiedClassName, + const char* methodName, + const char* fullyQualifiedParameters[], + size_t numParameters) { + return new ADynamicInstrumentationManager_MethodDescriptor(fullyQualifiedClassName, methodName, + fullyQualifiedParameters, + numParameters); +} + +void ADynamicInstrumentationManager_MethodDescriptor_destroy( + ADynamicInstrumentationManager_MethodDescriptor* instance) { + delete instance; +} + +struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets { + std::string containerPath; + uint64_t containerOffset; + uint64_t methodOffset; +}; + +ADynamicInstrumentationManager_ExecutableMethodFileOffsets* +ADynamicInstrumentationManager_ExecutableMethodFileOffsets_create() { + return new ADynamicInstrumentationManager_ExecutableMethodFileOffsets(); +} + +const char* ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) { + return instance->containerPath.c_str(); +} + +uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) { + return instance->containerOffset; +} + +uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) { + return instance->methodOffset; +} + +void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) { + delete instance; +} + +int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets( + const ADynamicInstrumentationManager_TargetProcess* targetProcess, + const ADynamicInstrumentationManager_MethodDescriptor* methodDescriptor, + ADynamicInstrumentationManager_ExecutableMethodFileOffsets** out) { + android::os::instrumentation::TargetProcess targetProcessParcel; + targetProcessParcel.uid = targetProcess->uid; + targetProcessParcel.pid = targetProcess->pid; + targetProcessParcel.processName = targetProcess->processName; + + android::os::instrumentation::MethodDescriptor methodDescriptorParcel; + methodDescriptorParcel.fullyQualifiedClassName = methodDescriptor->fqcn; + methodDescriptorParcel.methodName = methodDescriptor->methodName; + methodDescriptorParcel.fullyQualifiedParameters = methodDescriptor->fqParameters; + + sp<os::instrumentation::IDynamicInstrumentationManager> service = getService(); + if (service == nullptr) { + return INVALID_OPERATION; + } + + std::optional<android::os::instrumentation::ExecutableMethodFileOffsets> offsets; + binder_status_t result = + service->getExecutableMethodFileOffsets(targetProcessParcel, methodDescriptorParcel, + &offsets) + .exceptionCode(); + if (result != OK) { + return result; + } + + if (offsets != std::nullopt) { + auto* value = new ADynamicInstrumentationManager_ExecutableMethodFileOffsets(); + value->containerPath = offsets->containerPath; + value->containerOffset = offsets->containerOffset; + value->methodOffset = offsets->methodOffset; + *out = value; + } else { + *out = nullptr; + } + + return result; +}
\ No newline at end of file diff --git a/native/android/include_platform/android/dynamic_instrumentation_manager.h b/native/android/include_platform/android/dynamic_instrumentation_manager.h new file mode 100644 index 000000000000..6c46288954bf --- /dev/null +++ b/native/android/include_platform/android/dynamic_instrumentation_manager.h @@ -0,0 +1,132 @@ +/* + * 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. + */ + +#ifndef __ADYNAMICINSTRUMENTATIONMANAGER_H__ +#define __ADYNAMICINSTRUMENTATIONMANAGER_H__ + +#include <sys/cdefs.h> +#include <sys/types.h> + +__BEGIN_DECLS + +struct ADynamicInstrumentationManager_MethodDescriptor; +typedef struct ADynamicInstrumentationManager_MethodDescriptor + ADynamicInstrumentationManager_MethodDescriptor; + +struct ADynamicInstrumentationManager_TargetProcess; +typedef struct ADynamicInstrumentationManager_TargetProcess + ADynamicInstrumentationManager_TargetProcess; + +struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets; +typedef struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets + ADynamicInstrumentationManager_ExecutableMethodFileOffsets; + +/** + * Initializes an ADynamicInstrumentationManager_TargetProcess. Caller must clean up when they are + * done with ADynamicInstrumentationManager_TargetProcess_destroy. + * + * @param uid of targeted process. + * @param pid of targeted process. + * @param processName to disambiguate from corner cases that may arise from pid reuse. + */ +ADynamicInstrumentationManager_TargetProcess* _Nonnull + ADynamicInstrumentationManager_TargetProcess_create( + uid_t uid, pid_t pid, const char* _Nonnull processName) __INTRODUCED_IN(36); +/** + * Clean up an ADynamicInstrumentationManager_TargetProcess. + * + * @param instance returned from ADynamicInstrumentationManager_TargetProcess_create. + */ +void ADynamicInstrumentationManager_TargetProcess_destroy( + ADynamicInstrumentationManager_TargetProcess* _Nonnull instance) __INTRODUCED_IN(36); + +/** + * Initializes an ADynamicInstrumentationManager_MethodDescriptor. Caller must clean up when they + * are done with ADynamicInstrumentationManager_MethodDescriptor_Destroy. + * + * @param fullyQualifiedClassName fqcn of class containing the method. + * @param methodName + * @param fullyQualifiedParameters fqcn of parameters of the method's signature, or e.g. "int" for + * primitives. + * @param numParameters length of `fullyQualifiedParameters` array. + */ +ADynamicInstrumentationManager_MethodDescriptor* _Nonnull + ADynamicInstrumentationManager_MethodDescriptor_create( + const char* _Nonnull fullyQualifiedClassName, const char* _Nonnull methodName, + const char* _Nonnull fullyQualifiedParameters[_Nonnull], size_t numParameters) + __INTRODUCED_IN(36); +/** + * Clean up an ADynamicInstrumentationManager_MethodDescriptor. + * + * @param instance returned from ADynamicInstrumentationManager_MethodDescriptor_create. + */ +void ADynamicInstrumentationManager_MethodDescriptor_destroy( + ADynamicInstrumentationManager_MethodDescriptor* _Nonnull instance) __INTRODUCED_IN(36); + +/** + * Get the containerPath calculated by + * ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @return The OS path of the containing file. + */ +const char* _Nullable ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance) + __INTRODUCED_IN(36); +/** + * Get the containerOffset calculated by + * ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @return The offset of the containing file within the process' memory. + */ +uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance) + __INTRODUCED_IN(36); +/** + * Get the methodOffset calculated by ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + * @return The offset of the method within the containing file. + */ +uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance) + __INTRODUCED_IN(36); +/** + * Clean up an ADynamicInstrumentationManager_ExecutableMethodFileOffsets. + * + * @param instance returned from ADynamicInstrumentationManager_getExecutableMethodFileOffsets. + */ +void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy( + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance) + __INTRODUCED_IN(36); +/** + * Provides ART metadata about the described java method within the target process. + * + * @param targetProcess describes for which process the data is requested. + * @param methodDescriptor describes the targeted method. + * @param out will be populated with the data if successful. A nullptr combined + * with an OK status means that the program method is defined, but the offset + * info was unavailable because it is not AOT compiled. + * @return status indicating success or failure. The values correspond to the `binder_exception_t` + * enum values from <android/binder_status.h>. + */ +int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets( + const ADynamicInstrumentationManager_TargetProcess* _Nonnull targetProcess, + const ADynamicInstrumentationManager_MethodDescriptor* _Nonnull methodDescriptor, + ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull* _Nullable out) + __INTRODUCED_IN(36); + +__END_DECLS + +#endif // __ADYNAMICINSTRUMENTATIONMANAGER_H__ diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index b025cb880ee7..a0460572abfc 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -4,6 +4,15 @@ LIBANDROID { AActivityManager_removeUidImportanceListener; # systemapi introduced=31 AActivityManager_isUidActive; # systemapi introduced=31 AActivityManager_getUidImportance; # systemapi introduced=31 + ADynamicInstrumentationManager_TargetProcess_create; # systemapi + ADynamicInstrumentationManager_TargetProcess_destroy; # systemapi + ADynamicInstrumentationManager_MethodDescriptor_create; # systemapi + ADynamicInstrumentationManager_MethodDescriptor_destroy; # systemapi + ADynamicInstrumentationManager_getExecutableMethodFileOffsets; # systemapi + ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath; # systemapi + ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset; # systemapi + ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset; # systemapi + ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy; # systemapi AAssetDir_close; AAssetDir_getNextFileName; AAssetDir_rewind; diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl index 1346bd34e760..40fd0683f465 100644 --- a/nfc/java/android/nfc/INfcAdapter.aidl +++ b/nfc/java/android/nfc/INfcAdapter.aidl @@ -119,4 +119,5 @@ interface INfcAdapter boolean getSettingStatus(); boolean isTagPresent(); List<Entry> getRoutingTableEntryList(); + void indicateDataMigration(boolean inProgress, String pkg); } diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java index d9fd42fd4f7a..c5d8191b22e6 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -2795,11 +2795,8 @@ public final class NfcAdapter { @IntRange(from = 0, to = 15) int gid, @IntRange(from = 0) int oid, @NonNull byte[] payload) { Objects.requireNonNull(payload, "Payload must not be null"); - try { - return sService.sendVendorNciMessage(mt, gid, oid, payload); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return callServiceReturn(() -> sService.sendVendorNciMessage(mt, gid, oid, payload), + SEND_VENDOR_NCI_STATUS_FAILED); } /** @@ -2873,6 +2870,18 @@ public final class NfcAdapter { } /** + * Used by data migration to indicate data migration is in progrerss or not. + * + * Note: This is @hide intentionally since the client is inside the NFC apex. + * @param inProgress true if migration is in progress, false once done. + * @hide + */ + @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + public void indicateDataMigration(boolean inProgress) { + callService(() -> sService.indicateDataMigration(inProgress, mContext.getPackageName())); + } + + /** * Returns an instance of {@link NfcOemExtension} associated with {@link NfcAdapter} instance. * @hide */ diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index e1418678a9f8..b2dcb7fa4f53 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -61,7 +61,7 @@ android_library { "SettingsLibUtils", "SettingsLibZeroStatePreference", "settingslib_media_flags_lib", - "settingslib_flags_lib", + "aconfig_settingslib_flags_java_lib", ], plugins: ["androidx.room_room-compiler-plugin"], @@ -107,20 +107,6 @@ java_aconfig_library { aconfig_declarations: "settingslib_media_flags", } -aconfig_declarations { - name: "settingslib_flags", - package: "com.android.settingslib.flags", - container: "system", - srcs: [ - "aconfig/settingslib.aconfig", - ], -} - -java_aconfig_library { - name: "settingslib_flags_lib", - aconfig_declarations: "settingslib_flags", -} - soong_config_module_type { name: "avatar_picker_java_defaults", module_type: "java_defaults", diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java index 979ff96be3f7..993555e78bea 100644 --- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java +++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java @@ -37,7 +37,7 @@ import com.google.android.material.button.MaterialButton; /** * A preference handled a button */ -public class ButtonPreference extends Preference { +public class ButtonPreference extends Preference implements GroupSectionDividerMixin { enum ButtonStyle { FILLED_NORMAL(0, 0, R.layout.settingslib_expressive_button_filled), diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java index d60290ed91ef..37f47543c536 100644 --- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java +++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java @@ -43,7 +43,7 @@ import java.net.URISyntaxException; * A custom preference acting as "footer" of a page. It has a field for icon and text. It is added * to screen as the last preference. */ -public class FooterPreference extends Preference { +public class FooterPreference extends Preference implements GroupSectionDividerMixin { private static final String TAG = "FooterPreference"; public static final String KEY_FOOTER = "footer_preference"; diff --git a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt index c1578ef69635..1f8cfb5e432e 100644 --- a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt +++ b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt @@ -34,7 +34,7 @@ class StatusBannerPreference @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 -) : Preference(context, attrs, defStyleAttr, defStyleRes) { +) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin { enum class BannerStatus { GENERIC, diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt index 5be56f8ebc86..9764e64b8509 100644 --- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt +++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt @@ -31,7 +31,7 @@ open class TopIntroPreference @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 -) : Preference(context, attrs, defStyleAttr, defStyleRes) { +) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin { private var isCollapsable: Boolean = false private var minLines: Int = 2 diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 0dc772ab6ecd..ebd5a1deffd2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -224,7 +224,7 @@ public class BluetoothEventManager { // audio sharing is enabled. if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT && state == BluetoothAdapter.STATE_DISCONNECTED - && BluetoothUtils.isAudioSharingEnabled()) { + && BluetoothUtils.isAudioSharingUIAvailable(mContext)) { LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager(); if (profileManager != null && profileManager.getLeAudioBroadcastProfile() != null diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 612c193da9c3..a87b8153b858 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -64,6 +64,8 @@ public class BluetoothUtils { public static final int META_INT_ERROR = -1; public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled"; + public static final String DEVELOPER_OPTION_PREVIEW_KEY = + "bluetooth_le_audio_sharing_ui_preview_enabled"; private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH"; private static final Set<Integer> SA_PROFILES = @@ -643,6 +645,12 @@ public class BluetoothUtils { && connectedGroupIds.contains(groupId); } + /** Returns if the le audio sharing UI is available. */ + public static boolean isAudioSharingUIAvailable(@Nullable Context context) { + return isAudioSharingEnabled() || (context != null && isAudioSharingPreviewEnabled( + context.getContentResolver())); + } + /** Returns if the le audio sharing is enabled. */ public static boolean isAudioSharingEnabled() { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); @@ -653,7 +661,23 @@ public class BluetoothUtils { && adapter.isLeAudioBroadcastAssistantSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED; } catch (IllegalStateException e) { - Log.d(TAG, "LE state is on, but there is no bluetooth service.", e); + Log.d(TAG, "Fail to check isAudioSharingEnabled, e = ", e); + return false; + } + } + + /** Returns if the le audio sharing preview is enabled in developer option. */ + public static boolean isAudioSharingPreviewEnabled(@Nullable ContentResolver contentResolver) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + try { + return Flags.audioSharingDeveloperOption() + && getAudioSharingPreviewValue(contentResolver) + && adapter.isLeAudioBroadcastSourceSupported() + == BluetoothStatusCodes.FEATURE_SUPPORTED + && adapter.isLeAudioBroadcastAssistantSupported() + == BluetoothStatusCodes.FEATURE_SUPPORTED; + } catch (IllegalStateException e) { + Log.d(TAG, "Fail to check isAudioSharingPreviewEnabled, e = ", e); return false; } } @@ -996,6 +1020,17 @@ public class BluetoothUtils { BluetoothCsipSetCoordinator.GROUP_ID_INVALID); } + /** Get develop option value for audio sharing preview. */ + @WorkerThread + private static boolean getAudioSharingPreviewValue(@Nullable ContentResolver contentResolver) { + if (contentResolver == null) return false; + return Settings.Global.getInt( + contentResolver, + DEVELOPER_OPTION_PREVIEW_KEY, + 0 // value off + ) == 1; + } + /** Get secondary {@link CachedBluetoothDevice} in broadcast. */ @Nullable @WorkerThread diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 8641f7036c50..d0827b30efc9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1245,7 +1245,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> */ public String getConnectionSummary(boolean shortSummary) { CharSequence summary = null; - if (BluetoothUtils.isAudioSharingEnabled()) { + if (BluetoothUtils.isAudioSharingUIAvailable(mContext)) { if (mBluetoothManager == null) { mBluetoothManager = LocalBluetoothManager.getInstance(mContext, null); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java index b9f16edf6e77..4b7cb36f2753 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java @@ -383,11 +383,7 @@ public class CsipDeviceManager { preferredMainDevice.refresh(); hasChanged = true; } - if (isWorkProfile()) { - log("addMemberDevicesIntoMainDevice: skip sync source for work profile"); - } else { - syncAudioSharingSourceIfNeeded(preferredMainDevice); - } + syncAudioSharingSourceIfNeeded(preferredMainDevice); } if (hasChanged) { log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: " @@ -402,8 +398,12 @@ public class CsipDeviceManager { } private void syncAudioSharingSourceIfNeeded(CachedBluetoothDevice mainDevice) { - boolean isAudioSharingEnabled = BluetoothUtils.isAudioSharingEnabled(); + boolean isAudioSharingEnabled = BluetoothUtils.isAudioSharingUIAvailable(mContext); if (isAudioSharingEnabled) { + if (isWorkProfile()) { + log("addMemberDevicesIntoMainDevice: skip sync source for work profile"); + return; + } boolean hasBroadcastSource = BluetoothUtils.isBroadcasting(mBtManager) && BluetoothUtils.hasConnectedBroadcastSource( mainDevice, mBtManager); @@ -433,6 +433,8 @@ public class CsipDeviceManager { } } } + } else { + log("addMemberDevicesIntoMainDevice: skip sync source, flag disabled"); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java index 4f315a2a2486..76aa5bf3334c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java @@ -75,6 +75,24 @@ public final class InputRouteManager { @Override public void onAudioDevicesAdded(@NonNull AudioDeviceInfo[] addedDevices) { applyDefaultSelectedTypeToAllPresets(); + + // Activate the last hot plugged valid input device, to match the output device + // behavior. + @AudioDeviceType int deviceTypeToActivate = mSelectedInputDeviceType; + for (AudioDeviceInfo info : addedDevices) { + if (InputMediaDevice.isSupportedInputDevice(info.getType())) { + deviceTypeToActivate = info.getType(); + } + } + + // Only activate if we find a different valid input device. e.g. if none of the + // addedDevices is supported input device, we don't need to activate anything. + if (mSelectedInputDeviceType != deviceTypeToActivate) { + mSelectedInputDeviceType = deviceTypeToActivate; + AudioDeviceAttributes deviceAttributes = + createInputDeviceAttributes(mSelectedInputDeviceType); + setPreferredDeviceForAllPresets(deviceAttributes); + } } @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index 0e060dfdd447..6d481dbe64e9 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -29,11 +29,13 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothStatusCodes; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -48,6 +50,7 @@ import android.util.Pair; import com.android.internal.R; import com.android.settingslib.flags.Flags; +import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter; import com.android.settingslib.widget.AdaptiveIcon; import com.google.common.collect.ImmutableList; @@ -61,6 +64,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; import java.util.Collections; @@ -69,6 +74,7 @@ import java.util.List; import java.util.Set; @RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothAdapter.class}) public class BluetoothUtilsTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -88,6 +94,7 @@ public class BluetoothUtilsTest { @Mock private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState; private Context mContext; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; private static final String STRING_METADATA = "string_metadata"; private static final String BOOL_METADATA = "true"; private static final String INT_METADATA = "25"; @@ -109,6 +116,7 @@ public class BluetoothUtilsTest { mContext = spy(RuntimeEnvironment.application); mSetFlagsRule.disableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA); + mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager); when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager); when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast); @@ -1123,4 +1131,129 @@ public class BluetoothUtilsTest { AudioDeviceInfo.TYPE_HEARING_AID, address)); } + + @Test + public void isAudioSharingEnabled_flagOff_returnsFalse() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + + assertThat(BluetoothUtils.isAudioSharingEnabled()).isFalse(); + } + + @Test + public void isAudioSharingEnabled_featureNotSupported_returnsFalse() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( + BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); + mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + + assertThat(BluetoothUtils.isAudioSharingEnabled()).isFalse(); + } + + @Test + public void isAudioSharingEnabled_featureSupported_returnsTrue() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + + assertThat(BluetoothUtils.isAudioSharingEnabled()).isTrue(); + } + + @Test + public void isAudioSharingPreviewEnabled_flagOff_returnsFalse() { + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION); + + assertThat(BluetoothUtils.isAudioSharingPreviewEnabled( + mContext.getContentResolver())).isFalse(); + } + + @Test + public void isAudioSharingPreviewEnabled_featureNotSupported_returnsFalse() { + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION); + mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( + BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); + mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + + assertThat(BluetoothUtils.isAudioSharingPreviewEnabled( + mContext.getContentResolver())).isFalse(); + } + + @Test + public void isAudioSharingPreviewEnabled_developerOptionOff_returnsFalse() { + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION); + mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + Settings.Global.putInt(mContext.getContentResolver(), + BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 0); + + assertThat(BluetoothUtils.isAudioSharingPreviewEnabled( + mContext.getContentResolver())).isFalse(); + } + + @Test + public void isAudioSharingPreviewEnabled_developerOptionOn_returnsTrue() { + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION); + mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + Settings.Global.putInt(mContext.getContentResolver(), + BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 1); + + assertThat(BluetoothUtils.isAudioSharingPreviewEnabled( + mContext.getContentResolver())).isTrue(); + } + + @Test + public void isAudioSharingUIAvailable_audioSharingAndPreviewFlagOff_returnsFalse() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION); + + assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isFalse(); + } + + @Test + public void isAudioSharingUIAvailable_audioSharingAndPreviewDisabled_returnsFalse() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION); + mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( + BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); + mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + + assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isFalse(); + } + + @Test + public void isAudioSharingUIAvailable_audioSharingEnabled_returnsTrue() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION); + Settings.Global.putInt(mContext.getContentResolver(), + BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 0); + + assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isTrue(); + } + + @Test + public void isAudioSharingUIAvailable_audioSharingPreviewEnabled_returnsTrue() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION); + Settings.Global.putInt(mContext.getContentResolver(), + BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 1); + + assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isTrue(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java index 782cee23fb42..d808a25ebc04 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java @@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -138,6 +139,18 @@ public class InputRouteManagerTest { /* address= */ ""); } + private AudioDeviceAttributes getUsbHeadsetDeviceAttributes() { + return new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_INPUT, + AudioDeviceInfo.TYPE_USB_HEADSET, + /* address= */ ""); + } + + private AudioDeviceAttributes getHdmiDeviceAttributes() { + return new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_INPUT, AudioDeviceInfo.TYPE_HDMI, /* address= */ ""); + } + private void onPreferredDevicesForCapturePresetChanged(InputRouteManager inputRouteManager) { final List<AudioDeviceAttributes> audioDeviceAttributesList = new ArrayList<AudioDeviceAttributes>(); @@ -303,21 +316,47 @@ public class InputRouteManagerTest { } @Test - public void onAudioDevicesAdded_shouldApplyDefaultSelectedDeviceToAllPresets() { + public void onAudioDevicesAdded_shouldActivateAddedDevice() { final AudioManager audioManager = mock(AudioManager.class); - AudioDeviceAttributes wiredHeadsetDeviceAttributes = getWiredHeadsetDeviceAttributes(); - when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES)) - .thenReturn(Collections.singletonList(wiredHeadsetDeviceAttributes)); - InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager); AudioDeviceInfo[] devices = {mockWiredHeadsetInfo()}; inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices); - // Called twice, one after initiation, the other after onAudioDevicesAdded call. - verify(audioManager, atLeast(2)).getDevicesForAttributes(INPUT_ATTRIBUTES); + // The only added wired headset will be activated. for (@MediaRecorder.Source int preset : PRESETS) { - verify(audioManager, atLeast(2)) - .setPreferredDeviceForCapturePreset(preset, wiredHeadsetDeviceAttributes); + verify(audioManager, atLeast(1)) + .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes()); + } + } + + @Test + public void onAudioDevicesAdded_shouldActivateLastAddedDevice() { + final AudioManager audioManager = mock(AudioManager.class); + InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager); + AudioDeviceInfo[] devices = {mockWiredHeadsetInfo(), mockUsbHeadsetInfo()}; + inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices); + + // When adding multiple valid input devices, the last added device (usb headset in this + // case) will be activated. + for (@MediaRecorder.Source int preset : PRESETS) { + verify(audioManager, never()) + .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes()); + verify(audioManager, atLeast(1)) + .setPreferredDeviceForCapturePreset(preset, getUsbHeadsetDeviceAttributes()); + } + } + + @Test + public void onAudioDevicesAdded_doNotActivateInvalidAddedDevice() { + final AudioManager audioManager = mock(AudioManager.class); + InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager); + AudioDeviceInfo[] devices = {mockHdmiInfo()}; + inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices); + + // Do not activate since HDMI is not a valid input device. + for (@MediaRecorder.Source int preset : PRESETS) { + verify(audioManager, never()) + .setPreferredDeviceForCapturePreset(preset, getHdmiDeviceAttributes()); } } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 2c8c261fa8f8..0724410a2954 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -263,6 +263,8 @@ <uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE" /> + <uses-permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS" /> + <uses-permission android:name="android.permission.START_VIBRATION_SESSIONS" /> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" /> <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" /> <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" /> @@ -957,6 +959,9 @@ <!-- Permission required for CTS test - CtsTelephonyTestCases --> <uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" /> + <!-- Permission required for ExecutableMethodFileOffsetsTest --> + <uses-permission android:name="android.permission.DYNAMIC_INSTRUMENTATION" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING index 1820f39bb180..1903d22c93cc 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING +++ b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING @@ -1,8 +1,7 @@ { - // TODO: b/324945360 - Re-enable on presubmit after fixing failures "postsubmit": [ { "name": "AccessibilityMenuServiceTests" } ] -}
\ No newline at end of file +} diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 52512464cb30..b5eba08c8f87 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -118,3 +118,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "floating_menu_hearing_device_status_icon" + namespace: "accessibility" + description: "Update hearing device icon in floating menu according to the connection status." + bug: "357882387" +} diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 3bf3e24a2ba6..02b7667dc820 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -414,6 +414,17 @@ flag { } flag { + name: "status_bar_auto_start_screen_record_chip" + namespace: "systemui" + description: "When screen recording, use the specified start time to update the screen record " + "chip state instead of waiting for an official 'recording started' signal" + bug: "366448907" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "status_bar_use_repos_for_call_chip" namespace: "systemui" description: "Use repositories as the source of truth for call notifications shown as a chip in" @@ -620,9 +631,9 @@ flag { } flag { - name: "status_bar_simple_fragment" + name: "status_bar_root_modernization" namespace: "systemui" - description: "Feature flag for refactoring the collapsed status bar fragment" + description: "Feature flag for replacing the status bar fragment with a compose root" bug: "364360986" } @@ -1676,6 +1687,16 @@ flag { } flag { + name: "show_toast_when_app_control_brightness" + namespace: "systemui" + description: "Showing the warning toast if the current running app window has controlled the brightness value." + bug: "363225340" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "home_controls_dream_hsum" namespace: "systemui" description: "Enables the home controls dream in HSUM" @@ -1743,3 +1764,13 @@ flag { description: "An implementation of shortcut customizations through shortcut helper." bug: "365064144" } + +flag { + name: "stoppable_fgs_system_app" + namespace: "systemui" + description: "System app with foreground service can opt in to be stoppable." + bug: "376564917" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt index 00d905652987..300bdf2ffb01 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt @@ -48,7 +48,7 @@ class ViewHierarchyAnimator { Bound.LEFT to createViewProperty(Bound.LEFT), Bound.TOP to createViewProperty(Bound.TOP), Bound.RIGHT to createViewProperty(Bound.RIGHT), - Bound.BOTTOM to createViewProperty(Bound.BOTTOM) + Bound.BOTTOM to createViewProperty(Bound.BOTTOM), ) private fun createViewProperty(bound: Bound): IntProperty<View> { @@ -89,7 +89,7 @@ class ViewHierarchyAnimator { interpolator: Interpolator = DEFAULT_INTERPOLATOR, duration: Long = DEFAULT_DURATION, animateChildren: Boolean = true, - excludedViews: Set<View> = emptySet() + excludedViews: Set<View> = emptySet(), ): Boolean { return animate( rootView, @@ -97,7 +97,7 @@ class ViewHierarchyAnimator { duration, ephemeral = false, animateChildren = animateChildren, - excludedViews = excludedViews + excludedViews = excludedViews, ) } @@ -111,7 +111,7 @@ class ViewHierarchyAnimator { interpolator: Interpolator = DEFAULT_INTERPOLATOR, duration: Long = DEFAULT_DURATION, animateChildren: Boolean = true, - excludedViews: Set<View> = emptySet() + excludedViews: Set<View> = emptySet(), ): Boolean { return animate( rootView, @@ -119,7 +119,7 @@ class ViewHierarchyAnimator { duration, ephemeral = true, animateChildren = animateChildren, - excludedViews = excludedViews + excludedViews = excludedViews, ) } @@ -129,7 +129,7 @@ class ViewHierarchyAnimator { duration: Long, ephemeral: Boolean, animateChildren: Boolean, - excludedViews: Set<View> = emptySet() + excludedViews: Set<View> = emptySet(), ): Boolean { if ( !occupiesSpace( @@ -137,7 +137,7 @@ class ViewHierarchyAnimator { rootView.left, rootView.top, rootView.right, - rootView.bottom + rootView.bottom, ) ) { return false @@ -149,7 +149,7 @@ class ViewHierarchyAnimator { listener, recursive = true, animateChildren = animateChildren, - excludedViews = excludedViews + excludedViews = excludedViews, ) return true } @@ -164,7 +164,7 @@ class ViewHierarchyAnimator { private fun createUpdateListener( interpolator: Interpolator, duration: Long, - ephemeral: Boolean + ephemeral: Boolean, ): View.OnLayoutChangeListener { return createListener(interpolator, duration, ephemeral) } @@ -196,9 +196,9 @@ class ViewHierarchyAnimator { * * @param includeFadeIn true if the animator should also fade in the view and child views. * @param fadeInInterpolator the interpolator to use when fading in the view. Unused if - * [includeFadeIn] is false. - * @param onAnimationEnd an optional runnable that will be run once the animation - * finishes successfully. Will not be run if the animation is cancelled. + * [includeFadeIn] is false. + * @param onAnimationEnd an optional runnable that will be run once the animation finishes, + * regardless of whether the animation is cancelled or finishes successfully. */ @JvmOverloads fun animateAddition( @@ -217,7 +217,7 @@ class ViewHierarchyAnimator { rootView.left, rootView.top, rootView.right, - rootView.bottom + rootView.bottom, ) ) { return false @@ -241,7 +241,10 @@ class ViewHierarchyAnimator { // First, fade in the container view val containerDuration = duration / 6 createAndStartFadeInAnimator( - rootView, containerDuration, startDelay = 0, interpolator = fadeInInterpolator + rootView, + containerDuration, + startDelay = 0, + interpolator = fadeInInterpolator, ) // Then, fade in the child views @@ -253,7 +256,7 @@ class ViewHierarchyAnimator { childDuration, // Wait until the container fades in before fading in the children startDelay = containerDuration, - interpolator = fadeInInterpolator + interpolator = fadeInInterpolator, ) } // For now, we don't recursively fade in additional sub views (e.g. grandchild @@ -264,7 +267,7 @@ class ViewHierarchyAnimator { rootView, duration / 2, startDelay = 0, - interpolator = fadeInInterpolator + interpolator = fadeInInterpolator, ) } @@ -323,7 +326,7 @@ class ViewHierarchyAnimator { previousLeft: Int, previousTop: Int, previousRight: Int, - previousBottom: Int + previousBottom: Int, ) { if (view == null) return @@ -353,14 +356,14 @@ class ViewHierarchyAnimator { startTop, startRight, startBottom, - ignorePreviousValues + ignorePreviousValues, ) val endValues = mapOf( Bound.LEFT to left, Bound.TOP to top, Bound.RIGHT to right, - Bound.BOTTOM to bottom + Bound.BOTTOM to bottom, ) val boundsToAnimate = mutableSetOf<Bound>() @@ -396,8 +399,8 @@ class ViewHierarchyAnimator { * added on the side(s) of the [destination], the translation of those margins can be * included by specifying [includeMargins]. * - * @param onAnimationEnd an optional runnable that will be run once the animation finishes - * successfully. Will not be run if the animation is cancelled. + * @param onAnimationEnd an optional runnable that will be run once the animation finishes, + * regardless of whether the animation is cancelled or finishes successfully. */ @JvmOverloads fun animateRemoval( @@ -414,7 +417,7 @@ class ViewHierarchyAnimator { rootView.left, rootView.top, rootView.right, - rootView.bottom + rootView.bottom, ) ) { return false @@ -458,7 +461,7 @@ class ViewHierarchyAnimator { Bound.LEFT to rootView.left, Bound.TOP to rootView.top, Bound.RIGHT to rootView.right, - Bound.BOTTOM to rootView.bottom + Bound.BOTTOM to rootView.bottom, ) val endValues = processEndValuesForRemoval( @@ -550,7 +553,7 @@ class ViewHierarchyAnimator { destination: Hotspot, endValues: Map<Bound, Int>, interpolator: Interpolator, - duration: Long + duration: Long, ) { for (i in 0 until rootView.childCount) { val child = rootView.getChildAt(i) @@ -559,7 +562,7 @@ class ViewHierarchyAnimator { Bound.LEFT to child.left, Bound.TOP to child.top, Bound.RIGHT to child.right, - Bound.BOTTOM to child.bottom + Bound.BOTTOM to child.bottom, ) val childEndValues = processChildEndValuesForRemoval( @@ -569,7 +572,7 @@ class ViewHierarchyAnimator { child.right, child.bottom, endValues.getValue(Bound.RIGHT) - endValues.getValue(Bound.LEFT), - endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP) + endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP), ) val boundsToAnimate = mutableSetOf<Bound>() @@ -587,7 +590,7 @@ class ViewHierarchyAnimator { childEndValues, interpolator, duration, - ephemeral = true + ephemeral = true, ) } } @@ -601,7 +604,7 @@ class ViewHierarchyAnimator { left: Int, top: Int, right: Int, - bottom: Int + bottom: Int, ): Boolean { return visibility != View.GONE && left != right && top != bottom } @@ -616,6 +619,7 @@ class ViewHierarchyAnimator { * not newly introduced margins are included. * * Base case + * * ``` * 1) origin=TOP * x---------x x---------x x---------x x---------x x---------x @@ -636,9 +640,11 @@ class ViewHierarchyAnimator { * x-----x x-------x | | * x---------x * ``` + * * In case the start and end values differ in the direction of the origin, and * [ignorePreviousValues] is false, the previous values are used and a translation is * included in addition to the view expansion. + * * ``` * origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70) * x @@ -660,7 +666,7 @@ class ViewHierarchyAnimator { previousTop: Int, previousRight: Int, previousBottom: Int, - ignorePreviousValues: Boolean + ignorePreviousValues: Boolean, ): Map<Bound, Int> { val startLeft = if (ignorePreviousValues) newLeft else previousLeft val startTop = if (ignorePreviousValues) newTop else previousTop @@ -727,7 +733,7 @@ class ViewHierarchyAnimator { Bound.LEFT to left, Bound.TOP to top, Bound.RIGHT to right, - Bound.BOTTOM to bottom + Bound.BOTTOM to bottom, ) } @@ -777,18 +783,17 @@ class ViewHierarchyAnimator { includeMargins: Boolean = false, ): Map<Bound, Int> { val marginAdjustment = - if (includeMargins && - (rootView.layoutParams is ViewGroup.MarginLayoutParams)) { + if (includeMargins && (rootView.layoutParams is ViewGroup.MarginLayoutParams)) { val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams DimenHolder( left = marginLp.leftMargin, top = marginLp.topMargin, right = marginLp.rightMargin, - bottom = marginLp.bottomMargin + bottom = marginLp.bottomMargin, ) - } else { - DimenHolder(0, 0, 0, 0) - } + } else { + DimenHolder(0, 0, 0, 0) + } // These are the end values to use *if* this bound is part of the destination. val endLeft = left - marginAdjustment.left @@ -805,60 +810,69 @@ class ViewHierarchyAnimator { // - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight. return when (destination) { - Hotspot.TOP -> mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.LEFT to left, - Bound.RIGHT to right, - ) - Hotspot.TOP_RIGHT -> mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - ) - Hotspot.RIGHT -> mapOf( - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - Bound.TOP to top, - Bound.BOTTOM to bottom, - ) - Hotspot.BOTTOM_RIGHT -> mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - ) - Hotspot.BOTTOM -> mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.LEFT to left, - Bound.RIGHT to right, - ) - Hotspot.BOTTOM_LEFT -> mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - ) - Hotspot.LEFT -> mapOf( - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - Bound.TOP to top, - Bound.BOTTOM to bottom, - ) - Hotspot.TOP_LEFT -> mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - ) - Hotspot.CENTER -> mapOf( - Bound.LEFT to (endLeft + endRight) / 2, - Bound.RIGHT to (endLeft + endRight) / 2, - Bound.TOP to (endTop + endBottom) / 2, - Bound.BOTTOM to (endTop + endBottom) / 2, - ) + Hotspot.TOP -> + mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.LEFT to left, + Bound.RIGHT to right, + ) + Hotspot.TOP_RIGHT -> + mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + ) + Hotspot.RIGHT -> + mapOf( + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + Bound.TOP to top, + Bound.BOTTOM to bottom, + ) + Hotspot.BOTTOM_RIGHT -> + mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.RIGHT to endRight, + Bound.LEFT to endRight, + ) + Hotspot.BOTTOM -> + mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.LEFT to left, + Bound.RIGHT to right, + ) + Hotspot.BOTTOM_LEFT -> + mapOf( + Bound.BOTTOM to endBottom, + Bound.TOP to endBottom, + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + ) + Hotspot.LEFT -> + mapOf( + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + Bound.TOP to top, + Bound.BOTTOM to bottom, + ) + Hotspot.TOP_LEFT -> + mapOf( + Bound.TOP to endTop, + Bound.BOTTOM to endTop, + Bound.LEFT to endLeft, + Bound.RIGHT to endLeft, + ) + Hotspot.CENTER -> + mapOf( + Bound.LEFT to (endLeft + endRight) / 2, + Bound.RIGHT to (endLeft + endRight) / 2, + Bound.TOP to (endTop + endBottom) / 2, + Bound.BOTTOM to (endTop + endBottom) / 2, + ) } } @@ -887,7 +901,7 @@ class ViewHierarchyAnimator { right: Int, bottom: Int, parentWidth: Int, - parentHeight: Int + parentHeight: Int, ): Map<Bound, Int> { val halfWidth = (right - left) / 2 val halfHeight = (bottom - top) / 2 @@ -945,7 +959,7 @@ class ViewHierarchyAnimator { Bound.LEFT to endLeft, Bound.TOP to endTop, Bound.RIGHT to endRight, - Bound.BOTTOM to endBottom + Bound.BOTTOM to endBottom, ) } @@ -954,7 +968,7 @@ class ViewHierarchyAnimator { listener: View.OnLayoutChangeListener, recursive: Boolean = false, animateChildren: Boolean = true, - excludedViews: Set<View> = emptySet() + excludedViews: Set<View> = emptySet(), ) { if (excludedViews.contains(view)) return @@ -973,7 +987,7 @@ class ViewHierarchyAnimator { listener, recursive = true, animateChildren = animateChildren, - excludedViews = excludedViews + excludedViews = excludedViews, ) } } @@ -1027,7 +1041,7 @@ class ViewHierarchyAnimator { PropertyValuesHolder.ofInt( PROPERTIES[bound], startValues.getValue(bound), - endValues.getValue(bound) + endValues.getValue(bound), ) ) } @@ -1056,9 +1070,10 @@ class ViewHierarchyAnimator { // listener. recursivelyRemoveListener(view) } - if (!cancelled) { - onAnimationEnd?.run() - } + // Run the end runnable regardless of whether the animation was cancelled or + // not - this ensures critical actions (like removing a window) always occur + // (see b/344049884). + onAnimationEnd?.run() } override fun onAnimationCancel(animation: Animator) { @@ -1077,17 +1092,19 @@ class ViewHierarchyAnimator { view: View, duration: Long, startDelay: Long, - interpolator: Interpolator + interpolator: Interpolator, ) { val animator = ObjectAnimator.ofFloat(view, "alpha", 1f) animator.startDelay = startDelay animator.duration = duration animator.interpolator = interpolator - animator.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - view.setTag(R.id.tag_alpha_animator, null /* tag */) + animator.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + view.setTag(R.id.tag_alpha_animator, null /* tag */) + } } - }) + ) (view.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.cancel() view.setTag(R.id.tag_alpha_animator, animator) @@ -1105,7 +1122,7 @@ class ViewHierarchyAnimator { RIGHT, BOTTOM_RIGHT, BOTTOM, - BOTTOM_LEFT + BOTTOM_LEFT, } private enum class Bound(val label: String, val overrideTag: Int) { @@ -1147,14 +1164,10 @@ class ViewHierarchyAnimator { }; abstract fun setValue(view: View, value: Int) + abstract fun getValue(view: View): Int } /** Simple data class to hold a set of dimens for left, top, right, bottom. */ - private data class DimenHolder( - val left: Int, - val top: Int, - val right: Int, - val bottom: Int, - ) + private data class DimenHolder(val left: Int, val top: Int, val right: Int, val bottom: Int) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt index e9b7335197b0..eeab232542c0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt @@ -98,19 +98,20 @@ fun SystemUIDialogFactory.create( theme: Int = SystemUIDialog.DEFAULT_THEME, dismissOnDeviceLock: Boolean = SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK, @GravityInt dialogGravity: Int? = null, + dialogDelegate: DialogDelegate<SystemUIDialog> = + object : DialogDelegate<SystemUIDialog> { + override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { + super.onCreate(dialog, savedInstanceState) + dialogGravity?.let { dialog.window?.setGravity(it) } + } + }, content: @Composable (SystemUIDialog) -> Unit, ): ComponentSystemUIDialog { return create( context = context, theme = theme, dismissOnDeviceLock = dismissOnDeviceLock, - delegate = - object : DialogDelegate<SystemUIDialog> { - override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { - super.onCreate(dialog, savedInstanceState) - dialogGravity?.let { dialog.window?.setGravity(it) } - } - }, + delegate = dialogDelegate, content = content, ) } @@ -287,7 +288,7 @@ private fun DragHandle(dialog: Dialog) { Modifier.padding(top = 16.dp, bottom = 6.dp) .semantics { contentDescription = dragHandleContentDescription } .clickable { dialog.dismiss() }, - color = MaterialTheme.colorScheme.outlineVariant, + color = MaterialTheme.colorScheme.onSurfaceVariant, shape = MaterialTheme.shapes.extraLarge, ) { Box(Modifier.size(width = 32.dp, height = 4.dp)) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt index ae18aac66215..052e60e7ac9a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt @@ -25,6 +25,9 @@ interface SecureSettingsRepository { /** Returns a [Flow] tracking the value of a setting as an [Int]. */ fun intSetting(name: String, defaultValue: Int = 0): Flow<Int> + /** Returns a [Flow] tracking the value of a setting as a [Boolean]. */ + fun boolSetting(name: String, defaultValue: Boolean = false): Flow<Boolean> + /** Updates the value of the setting with the given name. */ suspend fun setInt(name: String, value: Int) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt index 8b9fcb496f59..9f37959663d7 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt @@ -63,6 +63,10 @@ class SecureSettingsRepositoryImpl( .flowOn(backgroundDispatcher) } + override fun boolSetting(name: String, defaultValue: Boolean): Flow<Boolean> { + return intSetting(name, if (defaultValue) 1 else 0).map { it != 0 } + } + override suspend fun setInt(name: String, value: Int) { withContext(backgroundDispatcher) { Settings.Secure.putInt(contentResolver, name, value) } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt index 8cda9b3d9985..b5f991cdb9b4 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt @@ -25,6 +25,9 @@ interface SystemSettingsRepository { /** Returns a [Flow] tracking the value of a setting as an [Int]. */ fun intSetting(name: String, defaultValue: Int = 0): Flow<Int> + /** Returns a [Flow] tracking the value of a setting as a [Boolean]. */ + fun boolSetting(name: String, defaultValue: Boolean = false): Flow<Boolean> + /** Updates the value of the setting with the given name. */ suspend fun setInt(name: String, value: Int) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt index b039a320b987..8485d4db2d20 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt @@ -63,6 +63,10 @@ class SystemSettingsRepositoryImpl( .flowOn(backgroundDispatcher) } + override fun boolSetting(name: String, defaultValue: Boolean): Flow<Boolean> { + return intSetting(name, if (defaultValue) 1 else 0).map { it != 0 } + } + override suspend fun setInt(name: String, value: Int) { withContext(backgroundDispatcher) { Settings.System.putInt(contentResolver, name, value) } } diff --git a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt index 37b97926afa4..21d8d4648824 100644 --- a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt +++ b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt @@ -28,6 +28,10 @@ class FakeSecureSettingsRepository : SecureSettingsRepository { return settings.map { it.getOrDefault(name, defaultValue.toString()) }.map { it.toInt() } } + override fun boolSetting(name: String, defaultValue: Boolean): Flow<Boolean> { + return intSetting(name, if (defaultValue) 1 else 0).map { it != 0 } + } + override suspend fun setInt(name: String, value: Int) { settings.value = settings.value.toMutableMap().apply { this[name] = value.toString() } } diff --git a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSystemSettingsRepository.kt b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSystemSettingsRepository.kt index 7da2b40fd57a..f6c053f5b1dc 100644 --- a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSystemSettingsRepository.kt +++ b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSystemSettingsRepository.kt @@ -28,6 +28,10 @@ class FakeSystemSettingsRepository : SystemSettingsRepository { return settings.map { it.getOrDefault(name, defaultValue.toString()) }.map { it.toInt() } } + override fun boolSetting(name: String, defaultValue: Boolean): Flow<Boolean> { + return intSetting(name, if (defaultValue) 1 else 0).map { it != 0 } + } + override suspend fun setInt(name: String, value: Int) { settings.value = settings.value.toMutableMap().apply { this[name] = value.toString() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt index a8c3af9488f0..8db82d58ecc5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt @@ -24,8 +24,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper -class -ViewHierarchyAnimatorTest : SysuiTestCase() { +class ViewHierarchyAnimatorTest : SysuiTestCase() { companion object { private const val TEST_DURATION = 1000L private val TEST_INTERPOLATOR = Interpolators.LINEAR @@ -49,9 +48,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) // animate() - var success = ViewHierarchyAnimator.animate( - rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION - ) + var success = + ViewHierarchyAnimator.animate( + rootView, + interpolator = TEST_INTERPOLATOR, + duration = TEST_DURATION, + ) rootView.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -64,9 +66,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { ViewHierarchyAnimator.stopAnimating(rootView) // animateNextUpdate() - success = ViewHierarchyAnimator.animateNextUpdate( - rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION - ) + success = + ViewHierarchyAnimator.animateNextUpdate( + rootView, + interpolator = TEST_INTERPOLATOR, + duration = TEST_DURATION, + ) rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) assertTrue(success) @@ -79,9 +84,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // animateAddition() rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + interpolator = TEST_INTERPOLATOR, + duration = TEST_DURATION, + ) rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) assertTrue(success) @@ -93,9 +101,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // animateRemoval() setUpRootWithChildren() val child = rootView.getChildAt(0) - success = ViewHierarchyAnimator.animateRemoval( - child, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION - ) + success = + ViewHierarchyAnimator.animateRemoval( + child, + interpolator = TEST_INTERPOLATOR, + duration = TEST_DURATION, + ) assertTrue(success) assertNotNull(child.getTag(R.id.tag_animator)) @@ -185,7 +196,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // Change all bounds. rootView.measure( View.MeasureSpec.makeMeasureSpec(190, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) + View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY), ) rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */) @@ -211,14 +222,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { fun animatesRootAndChildren_withExcludedViews() { setUpRootWithChildren() - val success = ViewHierarchyAnimator.animate( - rootView, - excludedViews = setOf(rootView.getChildAt(0)) - ) + val success = + ViewHierarchyAnimator.animate(rootView, excludedViews = setOf(rootView.getChildAt(0))) // Change all bounds. rootView.measure( - View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) + View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY), ) rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */) @@ -245,14 +254,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { fun animatesRootOnly() { setUpRootWithChildren() - val success = ViewHierarchyAnimator.animate( - rootView, - animateChildren = false - ) + val success = ViewHierarchyAnimator.animate(rootView, animateChildren = false) // Change all bounds. rootView.measure( - View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) + View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY), ) rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */) @@ -351,10 +357,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { fun animatesAppearingViewsRespectingOrigin() { // CENTER. rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */) - var success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.CENTER - ) + var success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.CENTER, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -364,10 +371,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // LEFT. rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.LEFT - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.LEFT, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -377,10 +385,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // TOP_LEFT. rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -390,10 +399,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // TOP. rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.TOP - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.TOP, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -403,10 +413,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // TOP_RIGHT. rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -416,10 +427,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // RIGHT. rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.RIGHT - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.RIGHT, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -429,10 +441,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // BOTTOM_RIGHT. rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -442,10 +455,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // BOTTOM. rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.BOTTOM - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.BOTTOM, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -455,10 +469,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // BOTTOM_LEFT. rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -471,11 +486,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { fun animatesAppearingViewsRespectingMargins() { // CENTER. rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */) - var success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.CENTER, - includeMargins = true - ) + var success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.CENTER, + includeMargins = true, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -485,10 +501,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // LEFT. rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, origin = ViewHierarchyAnimator.Hotspot.LEFT, - includeMargins = true - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.LEFT, + includeMargins = true, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -498,11 +516,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // TOP_LEFT. rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT, - includeMargins = true - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT, + includeMargins = true, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -512,10 +531,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // TOP. rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, origin = ViewHierarchyAnimator.Hotspot.TOP, - includeMargins = true - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.TOP, + includeMargins = true, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -525,11 +546,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // TOP_RIGHT. rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT, - includeMargins = true - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT, + includeMargins = true, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -539,11 +561,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // RIGHT. rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.RIGHT, - includeMargins = true - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.RIGHT, + includeMargins = true, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -553,11 +576,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // BOTTOM_RIGHT. rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT, - includeMargins = true - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT, + includeMargins = true, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -567,11 +591,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // BOTTOM. rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.BOTTOM, - includeMargins = true - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.BOTTOM, + includeMargins = true, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -581,11 +606,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // BOTTOM_LEFT. rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */) - success = ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT, - includeMargins = true - ) + success = + ViewHierarchyAnimator.animateAddition( + rootView, + origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT, + includeMargins = true, + ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) assertTrue(success) @@ -626,7 +652,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { ViewHierarchyAnimator.animateAddition( rootView, includeFadeIn = true, - fadeInInterpolator = Interpolators.LINEAR + fadeInInterpolator = Interpolators.LINEAR, ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) @@ -641,7 +667,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { ViewHierarchyAnimator.animateAddition( rootView, includeFadeIn = true, - fadeInInterpolator = Interpolators.LINEAR + fadeInInterpolator = Interpolators.LINEAR, ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) @@ -663,7 +689,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { ViewHierarchyAnimator.animateAddition( rootView, includeFadeIn = true, - fadeInInterpolator = Interpolators.LINEAR + fadeInInterpolator = Interpolators.LINEAR, ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) @@ -680,7 +706,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { ViewHierarchyAnimator.animateAddition( rootView, includeFadeIn = true, - fadeInInterpolator = Interpolators.LINEAR + fadeInInterpolator = Interpolators.LINEAR, ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) @@ -692,7 +718,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { ViewHierarchyAnimator.animateAddition( rootView, includeFadeIn = true, - fadeInInterpolator = Interpolators.LINEAR + fadeInInterpolator = Interpolators.LINEAR, ) // THEN the alpha remains at its current value (it doesn't get reset to 0) @@ -721,7 +747,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { ViewHierarchyAnimator.animateAddition( rootView, includeFadeIn = false, - fadeInInterpolator = Interpolators.LINEAR + fadeInInterpolator = Interpolators.LINEAR, ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) @@ -738,10 +764,10 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val onAnimationEndRunnable = { runnableRun = true } ViewHierarchyAnimator.animateAddition( - rootView, - origin = ViewHierarchyAnimator.Hotspot.CENTER, - includeMargins = true, - onAnimationEnd = onAnimationEndRunnable + rootView, + origin = ViewHierarchyAnimator.Hotspot.CENTER, + includeMargins = true, + onAnimationEnd = onAnimationEndRunnable, ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) @@ -751,7 +777,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { } @Test - fun animateAddition_runnableDoesNotRunWhenAnimationCancelled() { + fun animateAddition_runnableRunsWhenAnimationCancelled() { var runnableRun = false val onAnimationEndRunnable = { runnableRun = true } @@ -759,13 +785,13 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { rootView, origin = ViewHierarchyAnimator.Hotspot.CENTER, includeMargins = true, - onAnimationEnd = onAnimationEndRunnable + onAnimationEnd = onAnimationEndRunnable, ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) cancelAnimation(rootView) - assertEquals(false, runnableRun) + assertEquals(true, runnableRun) } @Test @@ -777,7 +803,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { rootView, origin = ViewHierarchyAnimator.Hotspot.CENTER, includeMargins = true, - onAnimationEnd = onAnimationEndRunnable + onAnimationEnd = onAnimationEndRunnable, ) rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */) @@ -791,11 +817,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { setUpRootWithChildren() val child = rootView.getChildAt(0) - val success = ViewHierarchyAnimator.animateRemoval( - child, - destination = ViewHierarchyAnimator.Hotspot.LEFT, - interpolator = Interpolators.LINEAR - ) + val success = + ViewHierarchyAnimator.animateRemoval( + child, + destination = ViewHierarchyAnimator.Hotspot.LEFT, + interpolator = Interpolators.LINEAR, + ) assertTrue(success) assertNotNull(child.getTag(R.id.tag_animator)) @@ -820,11 +847,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { rootView.addView(onlyChild) forceLayout() - val success = ViewHierarchyAnimator.animateRemoval( - onlyChild, - destination = ViewHierarchyAnimator.Hotspot.LEFT, - interpolator = Interpolators.LINEAR - ) + val success = + ViewHierarchyAnimator.animateRemoval( + onlyChild, + destination = ViewHierarchyAnimator.Hotspot.LEFT, + interpolator = Interpolators.LINEAR, + ) assertTrue(success) assertNotNull(onlyChild.getTag(R.id.tag_animator)) @@ -845,9 +873,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { setUpRootWithChildren() var removedChild = rootView.getChildAt(0) var remainingChild = rootView.getChildAt(1) - var success = ViewHierarchyAnimator.animateRemoval( - removedChild, destination = ViewHierarchyAnimator.Hotspot.CENTER - ) + var success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.CENTER, + ) // Ensure that the layout happens before the checks. forceLayout() @@ -863,9 +893,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { setUpRootWithChildren() removedChild = rootView.getChildAt(0) remainingChild = rootView.getChildAt(1) - success = ViewHierarchyAnimator.animateRemoval( - removedChild, destination = ViewHierarchyAnimator.Hotspot.LEFT - ) + success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.LEFT, + ) // Ensure that the layout happens before the checks. forceLayout() @@ -881,9 +913,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { setUpRootWithChildren() removedChild = rootView.getChildAt(0) remainingChild = rootView.getChildAt(1) - success = ViewHierarchyAnimator.animateRemoval( - removedChild, destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT - ) + success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT, + ) // Ensure that the layout happens before the checks. forceLayout() @@ -899,9 +933,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { setUpRootWithChildren() removedChild = rootView.getChildAt(0) remainingChild = rootView.getChildAt(1) - success = ViewHierarchyAnimator.animateRemoval( - removedChild, destination = ViewHierarchyAnimator.Hotspot.TOP - ) + success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.TOP, + ) // Ensure that the layout happens before the checks. forceLayout() @@ -917,9 +953,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { setUpRootWithChildren() removedChild = rootView.getChildAt(0) remainingChild = rootView.getChildAt(1) - success = ViewHierarchyAnimator.animateRemoval( - removedChild, destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT - ) + success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT, + ) // Ensure that the layout happens before the checks. forceLayout() @@ -935,9 +973,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { setUpRootWithChildren() removedChild = rootView.getChildAt(0) remainingChild = rootView.getChildAt(1) - success = ViewHierarchyAnimator.animateRemoval( - removedChild, destination = ViewHierarchyAnimator.Hotspot.RIGHT - ) + success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.RIGHT, + ) // Ensure that the layout happens before the checks. forceLayout() @@ -953,9 +993,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { setUpRootWithChildren() removedChild = rootView.getChildAt(0) remainingChild = rootView.getChildAt(1) - success = ViewHierarchyAnimator.animateRemoval( - removedChild, destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT - ) + success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT, + ) // Ensure that the layout happens before the checks. forceLayout() @@ -971,9 +1013,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { setUpRootWithChildren() removedChild = rootView.getChildAt(0) remainingChild = rootView.getChildAt(1) - success = ViewHierarchyAnimator.animateRemoval( - removedChild, destination = ViewHierarchyAnimator.Hotspot.BOTTOM - ) + success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.BOTTOM, + ) // Ensure that the layout happens before the checks. forceLayout() @@ -989,9 +1033,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { setUpRootWithChildren() removedChild = rootView.getChildAt(0) remainingChild = rootView.getChildAt(1) - success = ViewHierarchyAnimator.animateRemoval( - removedChild, destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT - ) + success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT, + ) // Ensure that the layout happens before the checks. forceLayout() @@ -1014,11 +1060,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val originalRight = removedChild.right val originalBottom = removedChild.bottom - val success = ViewHierarchyAnimator.animateRemoval( - removedChild, - destination = ViewHierarchyAnimator.Hotspot.CENTER, - includeMargins = true, - ) + val success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.CENTER, + includeMargins = true, + ) forceLayout() assertTrue(success) @@ -1027,13 +1074,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val expectedX = ((originalLeft - M_LEFT) + (originalRight + M_RIGHT)) / 2 val expectedY = ((originalTop - M_TOP) + (originalBottom + M_BOTTOM)) / 2 - checkBounds( - removedChild, - l = expectedX, - t = expectedY, - r = expectedX, - b = expectedY - ) + checkBounds(removedChild, l = expectedX, t = expectedY, r = expectedX, b = expectedY) } @Test @@ -1044,11 +1085,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val originalTop = removedChild.top val originalBottom = removedChild.bottom - val success = ViewHierarchyAnimator.animateRemoval( - removedChild, - destination = ViewHierarchyAnimator.Hotspot.LEFT, - includeMargins = true, - ) + val success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.LEFT, + includeMargins = true, + ) forceLayout() assertTrue(success) @@ -1059,7 +1101,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { l = originalLeft - M_LEFT, t = originalTop, r = originalLeft - M_LEFT, - b = originalBottom + b = originalBottom, ) } @@ -1070,11 +1112,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val originalLeft = removedChild.left val originalTop = removedChild.top - val success = ViewHierarchyAnimator.animateRemoval( - removedChild, - destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT, - includeMargins = true, - ) + val success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT, + includeMargins = true, + ) forceLayout() assertTrue(success) @@ -1085,7 +1128,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { l = originalLeft - M_LEFT, t = originalTop - M_TOP, r = originalLeft - M_LEFT, - b = originalTop - M_TOP + b = originalTop - M_TOP, ) } @@ -1097,11 +1140,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val originalTop = removedChild.top val originalRight = removedChild.right - val success = ViewHierarchyAnimator.animateRemoval( - removedChild, - destination = ViewHierarchyAnimator.Hotspot.TOP, - includeMargins = true, - ) + val success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.TOP, + includeMargins = true, + ) forceLayout() assertTrue(success) @@ -1112,7 +1156,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { l = originalLeft, t = originalTop - M_TOP, r = originalRight, - b = originalTop - M_TOP + b = originalTop - M_TOP, ) } @@ -1123,11 +1167,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val originalTop = removedChild.top val originalRight = removedChild.right - val success = ViewHierarchyAnimator.animateRemoval( - removedChild, - destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT, - includeMargins = true, - ) + val success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT, + includeMargins = true, + ) forceLayout() assertTrue(success) @@ -1138,7 +1183,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { l = originalRight + M_RIGHT, t = originalTop - M_TOP, r = originalRight + M_RIGHT, - b = originalTop - M_TOP + b = originalTop - M_TOP, ) } @@ -1150,11 +1195,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val originalRight = removedChild.right val originalBottom = removedChild.bottom - val success = ViewHierarchyAnimator.animateRemoval( - removedChild, - destination = ViewHierarchyAnimator.Hotspot.RIGHT, - includeMargins = true, - ) + val success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.RIGHT, + includeMargins = true, + ) forceLayout() assertTrue(success) @@ -1165,7 +1211,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { l = originalRight + M_RIGHT, t = originalTop, r = originalRight + M_RIGHT, - b = originalBottom + b = originalBottom, ) } @@ -1176,11 +1222,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val originalRight = removedChild.right val originalBottom = removedChild.bottom - val success = ViewHierarchyAnimator.animateRemoval( - removedChild, - destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT, - includeMargins = true, - ) + val success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT, + includeMargins = true, + ) forceLayout() assertTrue(success) @@ -1191,7 +1238,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { l = originalRight + M_RIGHT, t = originalBottom + M_BOTTOM, r = originalRight + M_RIGHT, - b = originalBottom + M_BOTTOM + b = originalBottom + M_BOTTOM, ) } @@ -1203,11 +1250,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val originalRight = removedChild.right val originalBottom = removedChild.bottom - val success = ViewHierarchyAnimator.animateRemoval( - removedChild, - destination = ViewHierarchyAnimator.Hotspot.BOTTOM, - includeMargins = true, - ) + val success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.BOTTOM, + includeMargins = true, + ) forceLayout() assertTrue(success) @@ -1218,7 +1266,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { l = originalLeft, t = originalBottom + M_BOTTOM, r = originalRight, - b = originalBottom + M_BOTTOM + b = originalBottom + M_BOTTOM, ) } @@ -1229,11 +1277,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val originalLeft = removedChild.left val originalBottom = removedChild.bottom - val success = ViewHierarchyAnimator.animateRemoval( - removedChild, - destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT, - includeMargins = true, - ) + val success = + ViewHierarchyAnimator.animateRemoval( + removedChild, + destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT, + includeMargins = true, + ) forceLayout() assertTrue(success) @@ -1244,9 +1293,10 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { l = originalLeft - M_LEFT, t = originalBottom + M_BOTTOM, r = originalLeft - M_LEFT, - b = originalBottom + M_BOTTOM + b = originalBottom + M_BOTTOM, ) } + /* ******** end of animatesViewRemoval_includeMarginsTrue tests ******** */ @Test @@ -1256,9 +1306,8 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val child = rootView.getChildAt(0) as ViewGroup val firstGrandChild = child.getChildAt(0) val secondGrandChild = child.getChildAt(1) - val success = ViewHierarchyAnimator.animateRemoval( - child, interpolator = Interpolators.LINEAR - ) + val success = + ViewHierarchyAnimator.animateRemoval(child, interpolator = Interpolators.LINEAR) assertTrue(success) assertNotNull(child.getTag(R.id.tag_animator)) @@ -1288,9 +1337,8 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val removedChild = rootView.getChildAt(0) val remainingChild = rootView.getChildAt(1) - val success = ViewHierarchyAnimator.animateRemoval( - removedChild, interpolator = Interpolators.LINEAR - ) + val success = + ViewHierarchyAnimator.animateRemoval(removedChild, interpolator = Interpolators.LINEAR) // Ensure that the layout happens before the checks. forceLayout() @@ -1315,17 +1363,14 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { forceLayout() val removedView = rootView.getChildAt(0) - ViewHierarchyAnimator.animateRemoval( - removedView, - onAnimationEnd = onAnimationEndRunnable - ) + ViewHierarchyAnimator.animateRemoval(removedView, onAnimationEnd = onAnimationEndRunnable) endAnimation(removedView) assertEquals(true, runnableRun) } @Test - fun animateRemoval_runnableDoesNotRunWhenAnimationCancelled() { + fun animateRemoval_runnableRunsWhenAnimationCancelled() { var runnableRun = false val onAnimationEndRunnable = { runnableRun = true } @@ -1333,13 +1378,10 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { forceLayout() val removedView = rootView.getChildAt(0) - ViewHierarchyAnimator.animateRemoval( - removedView, - onAnimationEnd = onAnimationEndRunnable - ) + ViewHierarchyAnimator.animateRemoval(removedView, onAnimationEnd = onAnimationEndRunnable) cancelAnimation(removedView) - assertEquals(false, runnableRun) + assertEquals(true, runnableRun) } @Test @@ -1351,10 +1393,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { forceLayout() val removedView = rootView.getChildAt(0) - ViewHierarchyAnimator.animateRemoval( - removedView, - onAnimationEnd = onAnimationEndRunnable - ) + ViewHierarchyAnimator.animateRemoval(removedView, onAnimationEnd = onAnimationEndRunnable) advanceAnimation(removedView, 0.5f) assertEquals(false, runnableRun) @@ -1370,7 +1409,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { rootView.addView(secondChild) rootView.measure( View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) + View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY), ) rootView.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */) @@ -1378,7 +1417,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { // Change all bounds. rootView.measure( View.MeasureSpec.makeMeasureSpec(150, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) + View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY), ) rootView.layout(0 /* l */, 0 /* t */, 150 /* r */, 100 /* b */) @@ -1501,7 +1540,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { checkBounds(rootView, l = 0, t = 15, r = 70, b = 80) // Change all bounds again. - rootView.layout(10 /* l */, 10 /* t */, 50/* r */, 50 /* b */) + rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) assertNull(rootView.getTag(R.id.tag_animator)) checkBounds(rootView, l = 10, t = 10, r = 50, b = 50) @@ -1523,7 +1562,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { ViewHierarchyAnimator.stopAnimating(rootView) // Change all bounds again. - rootView.layout(10 /* l */, 10 /* t */, 50/* r */, 50 /* b */) + rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) assertNull(rootView.getTag(R.id.tag_animator)) checkBounds(rootView, l = 10, t = 10, r = 50, b = 50) @@ -1543,10 +1582,8 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { val secondChild = View(mContext) rootView.addView(secondChild) - val firstChildParams = LinearLayout.LayoutParams( - 0 /* width */, - LinearLayout.LayoutParams.MATCH_PARENT - ) + val firstChildParams = + LinearLayout.LayoutParams(0 /* width */, LinearLayout.LayoutParams.MATCH_PARENT) firstChildParams.weight = 0.5f if (includeMarginsOnFirstChild) { firstChildParams.leftMargin = M_LEFT @@ -1556,23 +1593,25 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { } firstChild.layoutParams = firstChildParams - val secondChildParams = LinearLayout.LayoutParams( - 0 /* width */, - LinearLayout.LayoutParams.MATCH_PARENT - ) + val secondChildParams = + LinearLayout.LayoutParams(0 /* width */, LinearLayout.LayoutParams.MATCH_PARENT) secondChildParams.weight = 0.5f secondChild.layoutParams = secondChildParams firstGrandChild.layoutParams = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */) - (firstGrandChild.layoutParams as RelativeLayout.LayoutParams) - .addRule(RelativeLayout.ALIGN_PARENT_START) - (firstGrandChild.layoutParams as RelativeLayout.LayoutParams) - .addRule(RelativeLayout.ALIGN_PARENT_TOP) + (firstGrandChild.layoutParams as RelativeLayout.LayoutParams).addRule( + RelativeLayout.ALIGN_PARENT_START + ) + (firstGrandChild.layoutParams as RelativeLayout.LayoutParams).addRule( + RelativeLayout.ALIGN_PARENT_TOP + ) secondGrandChild.layoutParams = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */) - (secondGrandChild.layoutParams as RelativeLayout.LayoutParams) - .addRule(RelativeLayout.ALIGN_PARENT_END) - (secondGrandChild.layoutParams as RelativeLayout.LayoutParams) - .addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) + (secondGrandChild.layoutParams as RelativeLayout.LayoutParams).addRule( + RelativeLayout.ALIGN_PARENT_END + ) + (secondGrandChild.layoutParams as RelativeLayout.LayoutParams).addRule( + RelativeLayout.ALIGN_PARENT_BOTTOM + ) forceLayout() } @@ -1580,7 +1619,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() { private fun forceLayout() { rootView.measure( View.MeasureSpec.makeMeasureSpec(200 /* width */, View.MeasureSpec.AT_MOST), - View.MeasureSpec.makeMeasureSpec(100 /* height */, View.MeasureSpec.AT_MOST) + View.MeasureSpec.makeMeasureSpec(100 /* height */, View.MeasureSpec.AT_MOST), ) rootView.layout(0 /* l */, 0 /* t */, 200 /* r */, 100 /* b */) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt index 09831059a4b3..f4cffc5d962a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt @@ -20,7 +20,7 @@ import android.hardware.display.BrightnessInfo import android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE import android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF import android.hardware.display.DisplayManager -import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS +import android.hardware.display.DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -119,7 +119,8 @@ class ScreenBrightnessDisplayManagerRepositoryTest : SysuiTestCase() { .registerDisplayListener( capture(listenerCaptor), eq(null), - eq(EVENT_FLAG_DISPLAY_BRIGHTNESS), + eq(0), + eq(PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS), ) val newBrightness = BrightnessInfo(0.6f, 0.3f, 0.9f) @@ -157,7 +158,8 @@ class ScreenBrightnessDisplayManagerRepositoryTest : SysuiTestCase() { .registerDisplayListener( capture(listenerCaptor), eq(null), - eq(EVENT_FLAG_DISPLAY_BRIGHTNESS), + eq(0), + eq(PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS), ) changeBrightnessInfoAndNotify( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt index 116b7054ed5d..7cdfb0eb2451 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.coroutines.collectLastValue import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory +import com.android.systemui.kosmos.brightnessWarningToast import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R @@ -61,6 +62,7 @@ class BrightnessSliderViewModelTest : SysuiTestCase() { sliderHapticsViewModelFactory, brightnessMirrorShowingInteractor, supportsMirroring = true, + brightnessWarningToast, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt index cd8b2e12a3d5..e6e5665e7694 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt @@ -540,7 +540,8 @@ class DisplayRepositoryTest : SysuiTestCase() { .registerDisplayListener( connectedDisplayListener.capture(), eq(testHandler), - eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED), + eq(0), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED), ) return flowValue } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt index d1431eecfc68..1580ea5c1272 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt @@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts +import com.android.systemui.keyboard.shortcut.shortcutCustomizationDialogStarterFactory import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource @@ -71,7 +72,13 @@ class ShortcutHelperDialogStarterTest : SysuiTestCase() { private val starter: ShortcutHelperDialogStarter = with(kosmos) { - ShortcutHelperDialogStarter(coroutineScope, viewModel, dialogFactory, activityStarter) + ShortcutHelperDialogStarter( + coroutineScope, + viewModel, + shortcutCustomizationDialogStarterFactory, + dialogFactory, + activityStarter, + ) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java index 8ccaf6bc0651..0f631509bfba 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java @@ -300,7 +300,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0", UserHandle.USER_CURRENT); verifyActivityDetails("abc/.def"); - assertThat(mController.isEnabledForLockScreenButton()).isFalse(); + assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isAllowedOnLockScreen()).isTrue(); assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java index 16ae4662c2d4..0356422bda04 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java @@ -42,6 +42,7 @@ import android.content.pm.UserInfo; import android.os.Binder; import android.os.RemoteException; import android.os.UserHandle; +import android.platform.test.annotations.EnableFlags; import android.provider.DeviceConfig; import android.testing.TestableLooper; @@ -49,6 +50,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -315,13 +317,36 @@ public class FgsManagerControllerTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_STOPPABLE_FGS_SYSTEM_APP) + public void testButtonVisibilityOfStoppableApps() throws Exception { + setUserProfiles(0); + setBackgroundRestrictionExemptionReason("pkg", 12345, REASON_ALLOWLISTED_PACKAGE); + setBackgroundRestrictionExemptionReason("vendor_pkg", 67890, REASON_ALLOWLISTED_PACKAGE); + + // Same as above, but apps are opt-in to be stoppable + setStoppableApps(new String[] {"pkg"}, /* vendor */ false); + setStoppableApps(new String[] {"vendor_pkg"}, /* vendor */ true); + + final Binder binder = new Binder(); + setShowStopButtonForUserAllowlistedApps(true); + // Both are foreground. + mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true); + mIForegroundServiceObserver.onForegroundStateChanged(binder, "vendor_pkg", 0, true); + Assert.assertEquals(2, mFmc.visibleButtonsCount()); + + // The vendor package is no longer foreground. Only `pkg` remains. + mIForegroundServiceObserver.onForegroundStateChanged(binder, "vendor_pkg", 0, false); + Assert.assertEquals(1, mFmc.visibleButtonsCount()); + } + + @Test public void testShowUserVisibleJobsOnCreation() { // Test when the default is on. mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, "true", false); FgsManagerController fmc = new FgsManagerControllerImpl( - mContext, + mContext.getResources(), mMainExecutor, mBackgroundExecutor, mSystemClock, @@ -348,7 +373,7 @@ public class FgsManagerControllerTest extends SysuiTestCase { SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, "false", false); fmc = new FgsManagerControllerImpl( - mContext, + mContext.getResources(), mMainExecutor, mBackgroundExecutor, mSystemClock, @@ -446,6 +471,11 @@ public class FgsManagerControllerTest extends SysuiTestCase { .getBackgroundRestrictionExemptionReason(uid); } + private void setStoppableApps(String[] packageNames, boolean vendor) throws Exception { + overrideResource(vendor ? com.android.internal.R.array.vendor_stoppable_fgs_system_apps + : com.android.internal.R.array.stoppable_fgs_system_apps, packageNames); + } + FgsManagerController createFgsManagerController() throws RemoteException { ArgumentCaptor<IForegroundServiceObserver> iForegroundServiceObserverArgumentCaptor = ArgumentCaptor.forClass(IForegroundServiceObserver.class); @@ -455,7 +485,7 @@ public class FgsManagerControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(BroadcastReceiver.class); FgsManagerController result = new FgsManagerControllerImpl( - mContext, + mContext.getResources(), mMainExecutor, mBackgroundExecutor, mSystemClock, 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 5cba325450e6..03feceb7c15a 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 @@ -318,6 +318,63 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() } } + @Test + fun qqsMediaExpansion_collapsedMediaInLandscape() = + with(kosmos) { + testScope.testWithinLifecycle { + setCollapsedMediaInLandscape(true) + setMediaState(ACTIVE_MEDIA) + + setConfigurationForMediaInRow(mediaInRow = false) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED) + + setConfigurationForMediaInRow(mediaInRow = true) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.COLLAPSED) + } + } + + @Test + fun qqsMediaExpansion_notCollapsedMediaInLandscape_alwaysExpanded() = + with(kosmos) { + testScope.testWithinLifecycle { + setCollapsedMediaInLandscape(false) + setMediaState(ACTIVE_MEDIA) + + setConfigurationForMediaInRow(mediaInRow = false) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED) + + setConfigurationForMediaInRow(mediaInRow = true) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED) + } + } + + @Test + fun qqsMediaExpansion_reactsToChangesInCollapsedMediaInLandscape() = + with(kosmos) { + testScope.testWithinLifecycle { + setConfigurationForMediaInRow(mediaInRow = true) + setMediaState(ACTIVE_MEDIA) + + setCollapsedMediaInLandscape(false) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED) + + setCollapsedMediaInLandscape(true) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.COLLAPSED) + } + } + private fun TestScope.setMediaState(state: MediaState) { with(kosmos) { val activeMedia = state == ACTIVE_MEDIA @@ -331,6 +388,14 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() runCurrent() } + private fun TestScope.setCollapsedMediaInLandscape(collapsed: Boolean) { + with(kosmos) { + overrideResource(R.bool.config_quickSettingsMediaLandscapeCollapsed, collapsed) + fakeConfigurationRepository.onAnyConfigurationChange() + } + runCurrent() + } + companion object { private const val QS_DISABLE_FLAG = StatusBarManager.DISABLE2_QUICK_SETTINGS diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt index 41e2467f798b..ae7719bca2a8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt @@ -16,13 +16,19 @@ package com.android.systemui.settings.brightness +import android.hardware.display.BrightnessInfo import android.hardware.display.DisplayManager import android.os.Handler +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.service.vr.IVrManager import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogBuffer import com.android.systemui.settings.DisplayTracker @@ -30,13 +36,16 @@ import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -44,7 +53,8 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) @RunWithLooper class BrightnessControllerTest : SysuiTestCase() { - + @get:Rule + public val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() private val executor = FakeExecutor(FakeSystemClock()) private val secureSettings = FakeSettings() @Mock private lateinit var toggleSlider: ToggleSlider @@ -53,6 +63,7 @@ class BrightnessControllerTest : SysuiTestCase() { @Mock private lateinit var displayManager: DisplayManager @Mock private lateinit var iVrManager: IVrManager @Mock private lateinit var logger: LogBuffer + @Mock private lateinit var display: Display private lateinit var testableLooper: TestableLooper @@ -63,9 +74,11 @@ class BrightnessControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) + val contextSpy = spy(context) + whenever(contextSpy.getDisplay()).thenReturn(display) underTest = BrightnessController( - context, + contextSpy, toggleSlider, userTracker, displayTracker, @@ -105,4 +118,21 @@ class BrightnessControllerTest : SysuiTestCase() { assertThat(messagesProcessed).isEqualTo(1) } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_SHOW_TOAST_WHEN_APP_CONTROL_BRIGHTNESS) + fun testOnChange_showToastWhenAppOverridesBrightness() { + val brightnessInfo = BrightnessInfo( + 0.45f, 0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, + 1.0f /* highBrightnessTransitionPoint */, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, + true /* isBrightnessOverrideByWindow */ + ) + whenever(display.brightnessInfo).thenReturn(brightnessInfo) + underTest.registerCallbacks() + testableLooper.processAllMessages() + + underTest.onChanged(true /* tracking */, 100 /* value */, false /* stopTracking */) + verify(toggleSlider).showToast(any()) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt index 637a12c11690..3697c3151333 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.haptics.slider.HapticSlider import com.android.systemui.haptics.slider.HapticSliderPlugin import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.settings.brightness.ui.BrightnessWarningToast import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.BrightnessMirrorController import com.android.systemui.util.mockito.any @@ -64,6 +65,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() { @Mock private lateinit var vibratorHelper: VibratorHelper @Mock private lateinit var msdlPlayer: MSDLPlayer @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var brightnessWarningToast: BrightnessWarningToast @Captor private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener> @@ -94,6 +96,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() { HapticSlider.SeekBar(seekBar), ), activityStarter, + brightnessWarningToast, ) mController.init() mController.setOnChangedListener(listener) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java index 44782529e5c3..33a08035a7b2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java @@ -86,6 +86,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.user.domain.interactor.UserLogoutInteractor; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.wakelock.WakeLockFake; @@ -160,6 +161,8 @@ public class KeyguardIndicationControllerBaseTest extends SysuiTestCase { @Mock protected DeviceEntryFingerprintAuthInteractor mDeviceEntryFingerprintAuthInteractor; @Mock + protected UserLogoutInteractor mUserLogoutInteractor; + @Mock protected ScreenLifecycle mScreenLifecycle; @Mock protected AuthController mAuthController; @@ -248,6 +251,9 @@ public class KeyguardIndicationControllerBaseTest extends SysuiTestCase { when(mFaceHelpMessageDeferralFactory.create()).thenReturn(mFaceHelpMessageDeferral); when(mDeviceEntryFingerprintAuthInteractor.isEngaged()).thenReturn(mock(StateFlow.class)); + StateFlow mockLogoutEnabledFlow = mock(StateFlow.class); + when(mockLogoutEnabledFlow.getValue()).thenReturn(false); + when(mUserLogoutInteractor.isLogoutEnabled()).thenReturn(mockLogoutEnabledFlow); mIndicationHelper = new IndicationHelper(mKeyguardUpdateMonitor); @@ -291,7 +297,8 @@ public class KeyguardIndicationControllerBaseTest extends SysuiTestCase { KeyguardInteractorFactory.create(mFlags).getKeyguardInteractor(), mBiometricMessageInteractor, mDeviceEntryFingerprintAuthInteractor, - mDeviceEntryFaceAuthInteractor + mDeviceEntryFaceAuthInteractor, + mUserLogoutInteractor ); mController.init(); mController.setIndicationArea(mIndicationArea); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt index 0efd591940f2..11a125a21be0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt @@ -16,29 +16,35 @@ package com.android.systemui.statusbar.chips.screenrecord.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.Flags.FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) class ScreenRecordChipInteractorTest : SysuiTestCase() { - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope private val screenRecordRepo = kosmos.screenRecordRepository private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository @@ -116,6 +122,137 @@ class ScreenRecordChipInteractorTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) + fun screenRecordState_flagOff_doesNotAutomaticallySwitchToRecordingBasedOnTime() = + testScope.runTest { + val latest by collectLastValue(underTest.screenRecordState) + + // WHEN screen record should start in 900ms + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900) + assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900)) + + // WHEN 900ms has elapsed + advanceTimeBy(901) + + // THEN we don't automatically update to the recording state if the flag is off + assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900)) + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) + fun screenRecordState_flagOn_automaticallySwitchesToRecordingBasedOnTime() = + testScope.runTest { + val latest by collectLastValue(underTest.screenRecordState) + + // WHEN screen record should start in 900ms + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900) + assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900)) + + // WHEN 900ms has elapsed + advanceTimeBy(901) + + // THEN we automatically update to the recording state + assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = null)) + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) + fun screenRecordState_recordingBeginsEarly_switchesToRecording() = + testScope.runTest { + val latest by collectLastValue(underTest.screenRecordState) + + // WHEN screen record should start in 900ms + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900) + assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900)) + + // WHEN we update to the Recording state earlier than 900ms + advanceTimeBy(800) + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording + val task = createTask(taskId = 1) + mediaProjectionRepo.mediaProjectionState.value = + MediaProjectionState.Projecting.SingleTask( + "host.package", + hostDeviceName = null, + task, + ) + + // THEN we immediately switch to Recording, and we have the task + assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task)) + + // WHEN more than 900ms has elapsed + advanceTimeBy(200) + + // THEN we still stay in the Recording state and we have the task + assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task)) + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) + fun screenRecordState_secondRecording_doesNotAutomaticallyStart() = + testScope.runTest { + val latest by collectLastValue(underTest.screenRecordState) + + // First recording starts, records, and stops + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900) + advanceTimeBy(900) + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording + advanceTimeBy(5000) + screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing + advanceTimeBy(10000) + assertThat(latest).isEqualTo(ScreenRecordChipModel.DoingNothing) + + // WHEN a second recording is starting + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2900) + + // THEN we stay as starting and do not switch to Recording (verifying the auto-start + // timer is reset) + assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(2900)) + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) + fun screenRecordState_startingButThenDoingNothing_doesNotAutomaticallyStart() = + testScope.runTest { + val latest by collectLastValue(underTest.screenRecordState) + + // WHEN a screen recording is starting in 500ms + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(500) + assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(500)) + + // But it's cancelled after 300ms + advanceTimeBy(300) + screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing + + // THEN we don't automatically start the recording 200ms later + advanceTimeBy(201) + assertThat(latest).isEqualTo(ScreenRecordChipModel.DoingNothing) + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP) + fun screenRecordState_multipleStartingValues_autoStartResets() = + testScope.runTest { + val latest by collectLastValue(underTest.screenRecordState) + + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2900) + assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(2900)) + + advanceTimeBy(2800) + + // WHEN there's 100ms left to go before auto-start, but then we get a new start time + // that's in 500ms + screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(500) + + // THEN we don't auto-start in 100ms + advanceTimeBy(101) + assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(500)) + + // THEN we *do* auto-start 400ms later + advanceTimeBy(401) + assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = null)) + } + + @Test fun stopRecording_sendsToRepo() = testScope.runTest { assertThat(screenRecordRepo.stopRecordingInvoked).isFalse() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt index bfebe184ae2d..48d8add6b33a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt @@ -26,9 +26,8 @@ import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.mockDialogTransitionAnimator import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager @@ -44,6 +43,7 @@ import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory +import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import kotlin.test.Test @@ -61,7 +61,7 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class ScreenRecordChipViewModelTest : SysuiTestCase() { - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope private val screenRecordRepo = kosmos.screenRecordRepository private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository @@ -254,7 +254,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { MediaProjectionState.Projecting.SingleTask( "host.package", hostDeviceName = null, - FakeActivityTaskManager.createTask(taskId = 1) + FakeActivityTaskManager.createTask(taskId = 1), ) // THEN the start time is still the old start time @@ -275,12 +275,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message verify(kosmos.mockDialogTransitionAnimator) - .showFromView( - eq(mockSystemUIDialog), - eq(chipBackgroundView), - any(), - anyBoolean(), - ) + .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean()) } @Test @@ -297,12 +292,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message verify(kosmos.mockDialogTransitionAnimator) - .showFromView( - eq(mockSystemUIDialog), - eq(chipBackgroundView), - any(), - anyBoolean(), - ) + .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean()) } @Test @@ -314,7 +304,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { MediaProjectionState.Projecting.SingleTask( "host.package", hostDeviceName = null, - FakeActivityTaskManager.createTask(taskId = 1) + FakeActivityTaskManager.createTask(taskId = 1), ) val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener) @@ -323,12 +313,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { clickListener!!.onClick(chipView) // EndScreenRecordingDialogDelegate will test that the dialog has the right message verify(kosmos.mockDialogTransitionAnimator) - .showFromView( - eq(mockSystemUIDialog), - eq(chipBackgroundView), - any(), - anyBoolean(), - ) + .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean()) } @Test @@ -344,12 +329,7 @@ class ScreenRecordChipViewModelTest : SysuiTestCase() { val cujCaptor = argumentCaptor<DialogCuj>() verify(kosmos.mockDialogTransitionAnimator) - .showFromView( - any(), - any(), - cujCaptor.capture(), - anyBoolean(), - ) + .showFromView(any(), any(), cujCaptor.capture(), anyBoolean()) assertThat(cujCaptor.firstValue.cujType) .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt index e96def6d43a3..c5c2a94cf0ea 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt @@ -29,7 +29,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository @@ -48,6 +47,7 @@ import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory 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 +import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -72,7 +72,7 @@ import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @DisableFlags(StatusBarNotifChips.FLAG_NAME) class OngoingActivityChipsViewModelTest : SysuiTestCase() { - private val kosmos = Kosmos().also { it.testCase = this } + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val systemClock = kosmos.fakeSystemClock diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt index 938da88a8819..009b33b9f808 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt @@ -23,7 +23,6 @@ import android.platform.test.annotations.EnableFlags import android.view.ViewGroup import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.kosmos.useUnconfinedTestDispatcher @@ -81,21 +80,21 @@ class StatusBarInitializerTest : SysuiTestCase() { ) @Test - @EnableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT) - fun simpleFragment_startsFromCoreStartable() { + @EnableFlags(StatusBarRootModernization.FLAG_NAME) + fun flagOn_startsFromCoreStartable() { underTest.start() assertThat(underTest.initialized).isTrue() } @Test - @EnableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT) - fun simpleFragment_throwsIfInitializeIsCalled() { + @EnableFlags(StatusBarRootModernization.FLAG_NAME) + fun flagOn_throwsIfInitializeIsCalled() { assertThrows(IllegalStateException::class.java) { underTest.initializeStatusBar() } } @Test - @EnableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT) - fun simpleFragment_flagEnabled_doesNotCreateFragment() { + @EnableFlags(StatusBarRootModernization.FLAG_NAME) + fun flagOn_flagEnabled_doesNotCreateFragment() { underTest.start() verify(fragmentManager, never()).beginTransaction() @@ -103,14 +102,14 @@ class StatusBarInitializerTest : SysuiTestCase() { } @Test - @DisableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) fun flagOff_startCalled_stillInitializes() { underTest.start() assertThat(underTest.initialized).isTrue() } @Test - @DisableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) fun flagOff_doesNotThrowIfInitializeIsCalled() { underTest.initializeStatusBar() assertThat(underTest.initialized).isTrue() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index 3b5d358f7c2c..c4b1b841c6a5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification +import android.platform.test.flag.junit.FlagsParameterization import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase @@ -26,11 +26,17 @@ import com.android.systemui.communal.data.repository.communalSceneRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.scene.data.repository.Idle +import com.android.systemui.scene.data.repository.setSceneTransition +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.ShadeViewController.Companion.WAKEUP_ANIMATION_DELAY_MS import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController @@ -41,9 +47,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -55,16 +58,21 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.anyFloat -import org.mockito.Mockito.clearInvocations -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) -class NotificationWakeUpCoordinatorTest : SysuiTestCase() { +class NotificationWakeUpCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() { @get:Rule val animatorTestRule = AnimatorTestRule(this) @@ -105,6 +113,18 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { statusBarStateCallback.onDozeAmountChanged(dozeAmount, dozeAmount) } + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + @Before fun setup() { whenever(bypassController.bypassEnabled).then { bypassEnabled } @@ -178,6 +198,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun setDozeToZeroWhenCommunalShowingWillFullyHideNotifications() = testScope.runTest { val transitionState = @@ -192,6 +213,17 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun setDozeToZeroWhenCommunalShowingWillFullyHideNotifications_withSceneContainer() = + testScope.runTest { + kosmos.setSceneTransition(Idle(Scenes.Communal)) + setDozeAmount(0f) + verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f) + assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue() + } + + @Test + @DisableSceneContainer fun closingCommunalWillShowNotifications() = testScope.runTest { val transitionState = @@ -211,6 +243,20 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun closingCommunalWillShowNotifications_withSceneContainer() = + testScope.runTest { + kosmos.setSceneTransition(Idle(Scenes.Communal)) + setDozeAmount(0f) + verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f) + assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue() + + kosmos.setSceneTransition(Idle(CommunalScenes.Blank)) + verifyStackScrollerDozeAndHideAmount(dozeAmount = 0f, hideAmount = 0f) + assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse() + } + + @Test fun switchingToShadeWithBypassEnabledWillShowNotifications() { setDozeToZeroWithBypassWillFullyHideNotifications() clearInvocations(stackScrollerController) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt index 740abf359e92..76390fddc529 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt @@ -18,12 +18,13 @@ package com.android.systemui.statusbar.notification.icon.ui.viewmodel import android.content.res.mainResources import android.platform.test.annotations.DisableFlags -import androidx.test.ext.junit.runners.AndroidJUnit4 +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags +import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -51,17 +52,20 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidJUnit4::class) -class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class NotificationIconContainerAlwaysOnDisplayViewModelTest(flags: FlagsParameterization) : + SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, value = false) } } - val underTest = + val underTest by lazy { NotificationIconContainerAlwaysOnDisplayViewModel( kosmos.testDispatcher, kosmos.alwaysOnDisplayNotificationIconsInteractor, @@ -70,11 +74,24 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() { kosmos.mainResources, kosmos.shadeInteractor, ) + } val testScope = kosmos.testScope val keyguardRepository = kosmos.fakeKeyguardRepository val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository val powerRepository = kosmos.fakePowerRepository + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + @Before fun setup() { keyguardRepository.setKeyguardShowing(true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index 9dcbe1b591e5..ff0321b8cf2a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -99,6 +99,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { whenever(mainLooper.isCurrentThread).thenReturn(true) whenever(mainLooper.thread).thenReturn(thread) whenever(thread.name).thenReturn("backgroundThread") + whenever(context.applicationContext).thenReturn(context) whenever(context.resources).thenReturn(resources) whenever(context.mainExecutor).thenReturn(mContext.mainExecutor) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index b03c679a9c23..9b47eaddffd6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -17,13 +17,16 @@ package com.android.systemui.user.data.repository +import android.app.admin.devicePolicyManager import android.content.pm.UserInfo +import android.internal.statusbar.fakeStatusBarService import android.os.UserHandle import android.os.UserManager import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher @@ -57,6 +60,9 @@ class UserRepositoryImplTest : SysuiTestCase() { private val testDispatcher = kosmos.testDispatcher private val testScope = kosmos.testScope private val globalSettings = kosmos.fakeGlobalSettings + private val broadcastDispatcher = kosmos.broadcastDispatcher + private val devicePolicyManager = kosmos.devicePolicyManager + private val statusBarService = kosmos.fakeStatusBarService @Mock private lateinit var manager: UserManager @@ -317,6 +323,10 @@ class UserRepositoryImplTest : SysuiTestCase() { backgroundDispatcher = testDispatcher, globalSettings = globalSettings, tracker = tracker, + broadcastDispatcher = broadcastDispatcher, + devicePolicyManager = devicePolicyManager, + resources = context.resources, + statusBarService = statusBarService, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt new file mode 100644 index 000000000000..f70b42638cda --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt @@ -0,0 +1,126 @@ +/* + * 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.user.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.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class UserLogoutInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + + private val userRepository = kosmos.fakeUserRepository + private val testScope = kosmos.testScope + + private val underTest = kosmos.userLogoutInteractor + + @Before + fun setUp() { + userRepository.setUserInfos(USER_INFOS) + runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[2]) } + userRepository.setLogoutToSystemUserEnabled(false) + userRepository.setSecondaryUserLogoutEnabled(false) + } + + @Test + fun logOut_doesNothing_whenBothLogoutOptionsAreDisabled() { + testScope.runTest { + val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled) + val secondaryUserLogoutCount = userRepository.logOutSecondaryUserCallCount + val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount + assertThat(isLogoutEnabled).isFalse() + underTest.logOut() + assertThat(userRepository.logOutSecondaryUserCallCount) + .isEqualTo(secondaryUserLogoutCount) + assertThat(userRepository.logOutToSystemUserCallCount) + .isEqualTo(logoutToSystemUserCount) + } + } + + @Test + fun logOut_logsOutSecondaryUser_whenAdminEnabledSecondaryLogout() { + testScope.runTest { + val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled) + val lastLogoutCount = userRepository.logOutSecondaryUserCallCount + val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount + userRepository.setSecondaryUserLogoutEnabled(true) + assertThat(isLogoutEnabled).isTrue() + underTest.logOut() + assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount + 1) + assertThat(userRepository.logOutToSystemUserCallCount) + .isEqualTo(logoutToSystemUserCount) + } + } + + @Test + fun logOut_logsOutToSystemUser_whenLogoutToSystemUserIsEnabled() { + testScope.runTest { + val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled) + val lastLogoutCount = userRepository.logOutSecondaryUserCallCount + val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount + userRepository.setLogoutToSystemUserEnabled(true) + assertThat(isLogoutEnabled).isTrue() + underTest.logOut() + assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount) + assertThat(userRepository.logOutToSystemUserCallCount) + .isEqualTo(logoutToSystemUserCount + 1) + } + } + + @Test + fun logOut_secondaryUserTakesPrecedence() { + testScope.runTest { + val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled) + val lastLogoutCount = userRepository.logOutSecondaryUserCallCount + val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount + userRepository.setLogoutToSystemUserEnabled(true) + userRepository.setSecondaryUserLogoutEnabled(true) + assertThat(isLogoutEnabled).isTrue() + underTest.logOut() + assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount + 1) + assertThat(userRepository.logOutToSystemUserCallCount) + .isEqualTo(logoutToSystemUserCount) + } + } + + companion object { + private val USER_INFOS = + listOf( + UserInfo(0, "System user", 0), + UserInfo(10, "Regular user", 0), + UserInfo(11, "Secondary user", 0), + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt index e88dbd27fd37..ad473c09cf02 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt @@ -16,152 +16,16 @@ package com.android.systemui.util.settings.repository -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.coroutines.collectLastValue -import com.android.systemui.coroutines.collectValues -import com.android.systemui.kosmos.testScope -import com.android.systemui.testKosmos -import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository -import com.android.systemui.util.settings.fakeSettings -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test import org.junit.runner.RunWith -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) -class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() { +class UserAwareSecureSettingsRepositoryTest : UserAwareSettingsRepositoryTestBase() { - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - private val secureSettings = kosmos.fakeSettings - private val userRepository = kosmos.fakeUserRepository - private lateinit var underTest: UserAwareSecureSettingsRepository - - @Before - fun setup() { - underTest = kosmos.userAwareSecureSettingsRepository - - userRepository.setUserInfos(USER_INFOS) - - secureSettings.putBoolForUser(BOOL_SETTING_NAME, true, USER_1.id) - secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_2.id) - secureSettings.putIntForUser(INT_SETTING_NAME, 1337, USER_1.id) - secureSettings.putIntForUser(INT_SETTING_NAME, 818, USER_2.id) - } - - @Test - fun boolSetting_emitsInitialValue() { - testScope.runTest { - userRepository.setSelectedUserInfo(USER_1) - - val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false)) - - assertThat(enabled).isTrue() - } - } - - @Test - fun boolSetting_whenSettingChanges_emitsNewValue() { - testScope.runTest { - userRepository.setSelectedUserInfo(USER_1) - val enabled by collectValues(underTest.boolSetting(BOOL_SETTING_NAME, false)) - runCurrent() - - secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_1.id) - - assertThat(enabled).containsExactly(true, false).inOrder() - } - } - - @Test - fun boolSetting_whenWhenUserChanges_emitsNewValue() { - testScope.runTest { - userRepository.setSelectedUserInfo(USER_1) - val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false)) - runCurrent() - - userRepository.setSelectedUserInfo(USER_2) - - assertThat(enabled).isFalse() - } - } - - @Test - fun intSetting_emitsInitialValue() { - testScope.runTest { - userRepository.setSelectedUserInfo(USER_1) - - val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0)) - - assertThat(number).isEqualTo(1337) - } - } - - @Test - fun intSetting_whenSettingChanges_emitsNewValue() { - testScope.runTest { - userRepository.setSelectedUserInfo(USER_1) - val number by collectValues(underTest.intSetting(INT_SETTING_NAME, 0)) - runCurrent() - - secureSettings.putIntForUser(INT_SETTING_NAME, 1338, USER_1.id) - - assertThat(number).containsExactly(1337, 1338).inOrder() - } - } - - @Test - fun intSetting_whenWhenUserChanges_emitsNewValue() { - testScope.runTest { - userRepository.setSelectedUserInfo(USER_1) - val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0)) - runCurrent() - - userRepository.setSelectedUserInfo(USER_2) - - assertThat(number).isEqualTo(818) - } - } - - @Test - fun getInt_returnsInitialValue() = - testScope.runTest { - userRepository.setSelectedUserInfo(USER_1) - - assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(1337) - } - - @Test - fun getInt_whenSettingChanges_returnsNewValue() = - testScope.runTest { - userRepository.setSelectedUserInfo(USER_1) - secureSettings.putIntForUser(INT_SETTING_NAME, 999, USER_1.id) - - assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(999) - } - - @Test - fun getInt_whenUserChanges_returnsThatUserValue() = - testScope.runTest { - userRepository.setSelectedUserInfo(USER_2) - - assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(818) - } - - private companion object { - const val BOOL_SETTING_NAME = "BOOL_SETTING_NAME" - const val INT_SETTING_NAME = "INT_SETTING_NAME" - val USER_1 = UserInfo(/* id= */ 0, "user1", /* flags= */ 0) - val USER_2 = UserInfo(/* id= */ 1, "user2", /* flags= */ 0) - val USER_INFOS = listOf(USER_1, USER_2) + override fun getKosmosUserAwareSettingsRepository(): UserAwareSettingsRepository { + return kosmos.userAwareSecureSettingsRepository } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepositoryTestBase.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepositoryTestBase.kt new file mode 100644 index 000000000000..09db96f8ffb8 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepositoryTestBase.kt @@ -0,0 +1,163 @@ +/* + * 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.util.settings.repository + +import android.content.pm.UserInfo +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.settings.fakeSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +abstract class UserAwareSettingsRepositoryTestBase : SysuiTestCase() { + + protected val kosmos = testKosmos() + private val testScope = kosmos.testScope + protected val secureSettings = kosmos.fakeSettings + protected val userRepository = kosmos.fakeUserRepository + private lateinit var underTest: UserAwareSettingsRepository + + @Before + fun setup() { + underTest = getKosmosUserAwareSettingsRepository() + + userRepository.setUserInfos(USER_INFOS) + + secureSettings.putBoolForUser(BOOL_SETTING_NAME, true, USER_1.id) + secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_2.id) + secureSettings.putIntForUser(INT_SETTING_NAME, 1337, USER_1.id) + secureSettings.putIntForUser(INT_SETTING_NAME, 818, USER_2.id) + } + + abstract fun getKosmosUserAwareSettingsRepository(): UserAwareSettingsRepository + + @Test + fun boolSetting_emitsInitialValue() { + testScope.runTest { + userRepository.setSelectedUserInfo(USER_1) + + val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false)) + + assertThat(enabled).isTrue() + } + } + + @Test + fun boolSetting_whenSettingChanges_emitsNewValue() { + testScope.runTest { + userRepository.setSelectedUserInfo(USER_1) + val enabled by collectValues(underTest.boolSetting(BOOL_SETTING_NAME, false)) + runCurrent() + + secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_1.id) + + assertThat(enabled).containsExactly(true, false).inOrder() + } + } + + @Test + fun boolSetting_whenWhenUserChanges_emitsNewValue() { + testScope.runTest { + userRepository.setSelectedUserInfo(USER_1) + val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false)) + runCurrent() + + userRepository.setSelectedUserInfo(USER_2) + + assertThat(enabled).isFalse() + } + } + + @Test + fun intSetting_emitsInitialValue() { + testScope.runTest { + userRepository.setSelectedUserInfo(USER_1) + + val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0)) + + assertThat(number).isEqualTo(1337) + } + } + + @Test + fun intSetting_whenSettingChanges_emitsNewValue() { + testScope.runTest { + userRepository.setSelectedUserInfo(USER_1) + val number by collectValues(underTest.intSetting(INT_SETTING_NAME, 0)) + runCurrent() + + secureSettings.putIntForUser(INT_SETTING_NAME, 1338, USER_1.id) + + assertThat(number).containsExactly(1337, 1338).inOrder() + } + } + + @Test + fun intSetting_whenWhenUserChanges_emitsNewValue() { + testScope.runTest { + userRepository.setSelectedUserInfo(USER_1) + val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0)) + runCurrent() + + userRepository.setSelectedUserInfo(USER_2) + + assertThat(number).isEqualTo(818) + } + } + + @Test + fun getInt_returnsInitialValue() = + testScope.runTest { + userRepository.setSelectedUserInfo(USER_1) + + assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(1337) + } + + @Test + fun getInt_whenSettingChanges_returnsNewValue() = + testScope.runTest { + userRepository.setSelectedUserInfo(USER_1) + secureSettings.putIntForUser(INT_SETTING_NAME, 999, USER_1.id) + + assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(999) + } + + @Test + fun getInt_whenUserChanges_returnsThatUserValue() = + testScope.runTest { + userRepository.setSelectedUserInfo(USER_2) + + assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(818) + } + + private companion object { + const val BOOL_SETTING_NAME = "BOOL_SETTING_NAME" + const val INT_SETTING_NAME = "INT_SETTING_NAME" + val USER_1 = UserInfo(/* id= */ 0, "user1", /* flags= */ 0) + val USER_2 = UserInfo(/* id= */ 1, "user2", /* flags= */ 0) + val USER_INFOS = listOf(USER_1, USER_2) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepositoryTest.kt new file mode 100644 index 000000000000..586da8e541f6 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepositoryTest.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.settings.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.util.settings.data.repository.userAwareSystemSettingsRepository +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class UserAwareSystemSettingsRepositoryTest : UserAwareSettingsRepositoryTestBase() { + + override fun getKosmosUserAwareSettingsRepository(): UserAwareSettingsRepository { + return kosmos.userAwareSystemSettingsRepository + } +} diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 82c8c44f1efe..0854eb46ffdd 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -1086,4 +1086,9 @@ enable the desktop specific features. --> <bool name="config_enableDesktopFeatureSet">false</bool> + + <!-- + Whether the user switching can only happen by logging out and going through the system user (login screen). + --> + <bool name="config_userSwitchingMustGoThroughLoginScreen">false</bool> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 5a8417d7db9d..53ab686ff0d7 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -794,6 +794,7 @@ <!-- QuickSettings: Bluetooth secondary label shown when bluetooth is being enabled [CHAR LIMIT=NONE] --> <string name="quick_settings_bluetooth_secondary_label_transient">Turning on…</string> <!-- QuickSettings: Brightness [CHAR LIMIT=NONE] --> + <string name="quick_settings_brightness_unable_adjust_msg">Can\'t adjust brightness because it\'s being\n controlled by the top app</string> <!-- QuickSettings: Rotation Unlocked [CHAR LIMIT=NONE] --> <string name="quick_settings_rotation_unlocked_label">Auto-rotate</string> <!-- Accessibility label for Auto-ratate QuickSettings tile [CHAR LIMIT=NONE] --> @@ -3743,6 +3744,11 @@ is a component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> <string name="shortcut_helper_customize_mode_title">Customize keyboard shortcuts</string> + <!-- Sub title at the top of the keyboard shortcut helper customization dialog. Explains to the + user what action they need to take in the customization dialog to assign a new custom shortcut. + The helper is a component that shows the user which keyboard shortcuts they can use. + [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_customize_mode_sub_title">Press key to assign shortcut</string> <!-- Placeholder text shown in the search box of the keyboard shortcut helper, when the user hasn't typed in anything in the search box yet. The helper is a component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> @@ -3754,6 +3760,16 @@ use. The helper shows shortcuts in categories, which can be collapsed or expanded. [CHAR LIMIT=NONE] --> <string name="shortcut_helper_content_description_collapse_icon">Collapse icon</string> + <!-- Content description of the Meta key (also called Action Key) icon that prompts users to + press some key combination starting with meta key to assign new key combination to shortcut + in shortcut helper customization dialog. The helper is a component that shows the user + which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_content_description_meta_key">Action or Meta key icon</string> + <!-- Content description of the plus icon after the meta key icon prompts users to + press some key combination starting with meta key to assign new key combination to shortcut + in shortcut helper customization dialog. The helper is a component that shows the user + which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_content_description_plus_icon">Plus icon</string> <!-- Description text of the button that allows user to customize shortcuts in keyboard shortcut helper The helper is a component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> @@ -3784,6 +3800,24 @@ open keyboard settings while in shortcut helper. The helper is a component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> <string name="shortcut_helper_keyboard_settings_buttons_label">Keyboard Settings</string> + <!-- Label on the set shortcut button in keyboard shortcut helper customize dialog, that allows user to + confirm and assign key combination to selected shortcut. The helper is a component that + shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_customize_dialog_set_shortcut_button_label">Set shortcut</string> + <!-- Label on the cancel button in keyboard shortcut helper customize dialog, that allows user to + cancel and exit shortcut customization dialog, returning to the main shortcut helper page. + The helper is a component that shows the user which keyboard shortcuts they can use. + [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_customize_dialog_cancel_button_label">Cancel</string> + <!-- Placeholder text, prompting user to Press key combination assign to shortcut. This is shown + in shortcut helper's "Add Custom Shortcut" Dialog text field when user hasn't pressed + any key yet. The helper is a component that shows the user which keyboard shortcuts + they can use. [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_add_shortcut_dialog_placeholder">Press key</string> + <!-- Error message displayed when the user select a key combination that is already in use while + assigning a new custom key combination to a shortcut in shortcut helper. The helper is a + component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_customize_dialog_error_message">Key combination already in use. Try another key.</string> <!-- Keyboard touchpad tutorial scheduler--> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 6209ed82a0ad..e332280bc31a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -21,6 +21,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.Bundle; import android.os.UserHandle; +import android.view.KeyEvent; import android.view.MotionEvent; import com.android.internal.util.ScreenshotRequest; @@ -102,9 +103,9 @@ interface ISystemUiProxy { oneway void expandNotificationPanel() = 29; /** - * Notifies SystemUI to invoke Back. + * Notifies SystemUI of a back KeyEvent. */ - oneway void onBackPressed() = 44; + oneway void onBackEvent(in KeyEvent keyEvent) = 44; /** Sets home rotation enabled. */ oneway void setHomeRotationEnabled(boolean enabled) = 45; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index f98890ec9c5d..8ca0e807b31c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -219,7 +219,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_USER_UNLOCKED = 334; private static final int MSG_ASSISTANT_STACK_CHANGED = 335; private static final int MSG_BIOMETRIC_AUTHENTICATION_CONTINUE = 336; - private static final int MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED = 337; private static final int MSG_TELEPHONY_CAPABLE = 338; private static final int MSG_TIMEZONE_UPDATE = 339; private static final int MSG_USER_STOPPED = 340; @@ -402,7 +401,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; private boolean mFingerprintDetectRunning; private boolean mIsDreaming; - private boolean mLogoutEnabled; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private final FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider; @@ -1739,9 +1737,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState)); } else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) { mHandler.sendEmptyMessage(MSG_SIM_SUBSCRIPTION_INFO_CHANGED); - } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals( - action)) { - mHandler.sendEmptyMessage(MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED); } } }; @@ -2328,9 +2323,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE: updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); break; - case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED: - updateLogoutEnabled(); - break; case MSG_TELEPHONY_CAPABLE: updateTelephonyCapable((boolean) msg.obj); break; @@ -2496,7 +2488,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab boolean isUserUnlocked = mUserManager.isUserUnlocked(user); mLogger.logUserUnlockedInitialState(user, isUserUnlocked); mUserIsUnlocked.put(user, isUserUnlocked); - mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled(); updateSecondaryLockscreenRequirement(user); List<UserInfo> allUsers = mUserManager.getUsers(); for (UserInfo userInfo : allUsers) { @@ -4060,28 +4051,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return null; // not found } - /** - * @return a cached version of DevicePolicyManager.isLogoutEnabled() - */ - public boolean isLogoutEnabled() { - return mLogoutEnabled; - } - - private void updateLogoutEnabled() { - Assert.isMainThread(); - boolean logoutEnabled = mDevicePolicyManager.isLogoutEnabled(); - if (mLogoutEnabled != logoutEnabled) { - mLogoutEnabled = logoutEnabled; - - for (int i = 0; i < mCallbacks.size(); i++) { - KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); - if (cb != null) { - cb.onLogoutEnabledChanged(); - } - } - } - } - protected int getBiometricLockoutDelay() { return BIOMETRIC_LOCKOUT_RESET_DELAY_MS; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 7ac5ac229793..fdee21bcc479 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -286,11 +286,6 @@ public class KeyguardUpdateMonitorCallback { public void onTrustAgentErrorMessage(CharSequence message) { } /** - * Called when a value of logout enabled is change. - */ - public void onLogoutEnabledChanged() { } - - /** * Called when authenticated fingerprint biometrics are cleared. */ public void onFingerprintsCleared() { } diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt index 06d391704870..6c78b8b0e58a 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt @@ -19,6 +19,7 @@ package com.android.systemui.brightness.data.repository import android.annotation.SuppressLint import android.hardware.display.BrightnessInfo import android.hardware.display.DisplayManager +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.brightness.shared.model.BrightnessLog import com.android.systemui.brightness.shared.model.LinearBrightness import com.android.systemui.brightness.shared.model.formatBrightness @@ -46,7 +47,6 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext /** @@ -64,6 +64,9 @@ interface ScreenBrightnessRepository { /** Current maximum value for the brightness */ val maxLinearBrightness: Flow<LinearBrightness> + /** Whether the current brightness value is overridden by the application window */ + val isBrightnessOverriddenByWindow: StateFlow<Boolean> + /** Gets the current values for min and max brightness */ suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> @@ -90,10 +93,7 @@ constructor( @Background private val backgroundContext: CoroutineContext, ) : ScreenBrightnessRepository { - private val apiQueue = - Channel<SetBrightnessMethod>( - capacity = UNLIMITED, - ) + private val apiQueue = Channel<SetBrightnessMethod>(capacity = UNLIMITED) init { applicationScope.launch(context = backgroundContext) { @@ -132,7 +132,8 @@ constructor( displayManager.registerDisplayListener( listener, null, - DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, + /* eventFlags */ 0, + DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS, ) awaitClose { displayManager.unregisterDisplayListener(listener) } @@ -180,6 +181,11 @@ constructor( .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_BRIGHTNESS, null) .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f)) + override val isBrightnessOverriddenByWindow = brightnessInfo + .filterNotNull() + .map { it.isBrightnessOverrideByWindow } + .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false) + override fun setTemporaryBrightness(value: LinearBrightness) { apiQueue.trySend(SetBrightnessMethod.Temporary(value)) } @@ -190,8 +196,10 @@ constructor( private sealed interface SetBrightnessMethod { val value: LinearBrightness + @JvmInline value class Temporary(override val value: LinearBrightness) : SetBrightnessMethod + @JvmInline value class Permanent(override val value: LinearBrightness) : SetBrightnessMethod } @@ -201,7 +209,7 @@ constructor( LOG_BUFFER_BRIGHTNESS_CHANGE_TAG, if (permanent) LogLevel.DEBUG else LogLevel.VERBOSE, { str1 = value.formatBrightness() }, - { "Change requested: $str1" } + { "Change requested: $str1" }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt index 5647f521762f..b794c5c85645 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt @@ -25,12 +25,12 @@ import com.android.systemui.brightness.shared.model.logDiffForTable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.table.TableLogBuffer -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject /** * Converts between [GammaBrightness] and [LinearBrightness]. @@ -68,6 +68,8 @@ constructor( .stateIn(applicationScope, SharingStarted.WhileSubscribed(), GammaBrightness(0)) } + val brightnessOverriddenByWindow = screenBrightnessRepository.isBrightnessOverriddenByWindow + /** Sets the brightness temporarily, while the user is changing it. */ suspend fun setTemporaryBrightness(gammaBrightness: GammaBrightness) { screenBrightnessRepository.setTemporaryBrightness( diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt index 02161d255abc..917a4ff9036b 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt @@ -19,6 +19,7 @@ package com.android.systemui.brightness.ui.compose import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -27,8 +28,10 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue @@ -38,6 +41,7 @@ import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -62,6 +66,7 @@ import com.android.systemui.utils.PolicyRestriction @Composable private fun BrightnessSlider( + viewModel: BrightnessSliderViewModel, gammaValue: Int, valueRange: IntRange, label: Text.Resource, @@ -97,21 +102,31 @@ private fun BrightnessSlider( null } + val overriddenByAppState by if (Flags.showToastWhenAppControlBrightness()) { + viewModel.brightnessOverriddenByWindow.collectAsStateWithLifecycle() + } else { + mutableStateOf(false) + } + PlatformSlider( value = animatedValue, valueRange = floatValueRange, enabled = !isRestricted, onValueChange = { if (!isRestricted) { - hapticsViewModel?.onValueChange(it) - value = it.toInt() - onDrag(value) + if (!overriddenByAppState) { + hapticsViewModel?.onValueChange(it) + value = it.toInt() + onDrag(value) + } } }, onValueChangeFinished = { if (!isRestricted) { - hapticsViewModel?.onValueChangeEnded() - onStop(value) + if (!overriddenByAppState) { + hapticsViewModel?.onValueChangeEnded() + onStop(value) + } } }, modifier = @@ -136,6 +151,21 @@ private fun BrightnessSlider( }, interactionSource = interactionSource, ) + // Showing the warning toast if the current running app window has controlled the + // brightness value. + if (Flags.showToastWhenAppControlBrightness()) { + val context = LocalContext.current + LaunchedEffect(interactionSource) { + interactionSource.interactions.collect { interaction -> + if (interaction is DragInteraction.Start && overriddenByAppState) { + viewModel.showToast( + context, + R.string.quick_settings_brightness_unable_adjust_msg + ) + } + } + } + } } private val sliderBackgroundFrameSize = 8.dp @@ -167,6 +197,7 @@ fun BrightnessSliderContainer( Box(modifier = modifier.fillMaxWidth().sysuiResTag("brightness_slider")) { BrightnessSlider( + viewModel = viewModel, gammaValue = gamma, valueRange = viewModel.minBrightness.value..viewModel.maxBrightness.value, label = viewModel.label, diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt index a61ce8f62522..1630ee58449f 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt @@ -17,7 +17,8 @@ package com.android.systemui.brightness.ui.viewmodel import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue +import android.content.Context +import androidx.annotation.StringRes import com.android.systemui.brightness.domain.interactor.BrightnessPolicyEnforcementInteractor import com.android.systemui.brightness.domain.interactor.ScreenBrightnessInteractor import com.android.systemui.brightness.shared.model.GammaBrightness @@ -29,6 +30,7 @@ import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.res.R import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor +import com.android.systemui.settings.brightness.ui.BrightnessWarningToast import com.android.systemui.utils.PolicyRestriction import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -51,6 +53,7 @@ constructor( val hapticsViewModelFactory: SliderHapticsViewModel.Factory, private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor, @Assisted private val supportsMirroring: Boolean, + private val brightnessWarningToast: BrightnessWarningToast, ) : ExclusiveActivatable() { private val hydrator = Hydrator("BrightnessSliderViewModel.hydrator") @@ -75,6 +78,15 @@ constructor( brightnessPolicyEnforcementInteractor.startAdminSupportDetailsDialog(restriction) } + val brightnessOverriddenByWindow = screenBrightnessInteractor.brightnessOverriddenByWindow + + fun showToast(viewContext: Context, @StringRes resId: Int) { + if (brightnessWarningToast.isToastActive()) { + return + } + brightnessWarningToast.show(viewContext, resId) + } + /** * As a brightness slider is dragged, the corresponding events should be sent using this method. */ diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt index 034cb31dbc74..1fa829a675ec 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt @@ -264,7 +264,8 @@ constructor( displayManager.registerDisplayListener( callback, backgroundHandler, - DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED, + /* eventFlags */ 0, + DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED, ) awaitClose { displayManager.unregisterDisplayListener(callback) } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 162047bb3b79..91b44e7a6202 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -36,7 +36,6 @@ import android.app.Dialog; import android.app.IActivityManager; import android.app.StatusBarManager; import android.app.WallpaperManager; -import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -138,6 +137,7 @@ import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.android.systemui.user.domain.interactor.UserLogoutInteractor; import com.android.systemui.util.EmergencyDialerConstants; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.settings.GlobalSettings; @@ -197,7 +197,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final Context mContext; private final GlobalActionsManager mWindowManagerFuncs; private final AudioManager mAudioManager; - private final DevicePolicyManager mDevicePolicyManager; private final LockPatternUtils mLockPatternUtils; private final SelectedUserInteractor mSelectedUserInteractor; private final TelephonyListenerManager mTelephonyListenerManager; @@ -260,6 +259,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final ShadeController mShadeController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final DialogTransitionAnimator mDialogTransitionAnimator; + private final UserLogoutInteractor mLogoutInteractor; private final GlobalActionsInteractor mInteractor; @VisibleForTesting @@ -344,7 +344,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene Context context, GlobalActionsManager windowManagerFuncs, AudioManager audioManager, - DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils, BroadcastDispatcher broadcastDispatcher, TelephonyListenerManager telephonyListenerManager, @@ -376,11 +375,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene KeyguardUpdateMonitor keyguardUpdateMonitor, DialogTransitionAnimator dialogTransitionAnimator, SelectedUserInteractor selectedUserInteractor, + UserLogoutInteractor logoutInteractor, GlobalActionsInteractor interactor) { mContext = context; mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; - mDevicePolicyManager = devicePolicyManager; mLockPatternUtils = lockPatternUtils; mTelephonyListenerManager = telephonyListenerManager; mKeyguardStateController = keyguardStateController; @@ -412,6 +411,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mKeyguardUpdateMonitor = keyguardUpdateMonitor; mDialogTransitionAnimator = dialogTransitionAnimator; mSelectedUserInteractor = selectedUserInteractor; + mLogoutInteractor = logoutInteractor; mInteractor = interactor; // receive broadcasts @@ -639,12 +639,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) { addIfShouldShowAction(tempActions, new ScreenshotAction()); } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) { - // TODO(b/206032495): should call mDevicePolicyManager.getLogoutUserId() instead of - // hardcode it to USER_SYSTEM so it properly supports headless system user mode - // (and then call mDevicePolicyManager.clearLogoutUser() after switched) - if (mDevicePolicyManager.isLogoutEnabled() - && currentUser.get() != null - && currentUser.get().id != UserHandle.USER_SYSTEM) { + if (mLogoutInteractor.isLogoutEnabled().getValue()) { addIfShouldShowAction(tempActions, new LogoutAction()); } } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) { @@ -1134,7 +1129,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene // Add a little delay before executing, to give the dialog a chance to go away before // switching user mHandler.postDelayed(() -> { - mDevicePolicyManager.logoutUser(); + mLogoutInteractor.logOut(); }, mDialogPressDelay); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt new file mode 100644 index 000000000000..85d22144f201 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.domain.interactor + +import android.view.KeyEvent.META_META_ON +import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey +import javax.inject.Inject + +class ShortcutCustomizationInteractor @Inject constructor() { + fun getDefaultCustomShortcutModifierKey(): ShortcutKey.Icon.ResIdIcon { + return ShortcutKey.Icon.ResIdIcon(ShortcutHelperKeys.keyIcons[META_META_ON]!!) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt new file mode 100644 index 000000000000..e4ccc2c553fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.shared.model + +data class ShortcutInfo( + val label: String, + val categoryType: ShortcutCategoryType, + val subCategoryLabel: String, +) diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogDelegate.kt new file mode 100644 index 000000000000..c98472ef7bda --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogDelegate.kt @@ -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. + */ + +package com.android.systemui.keyboard.shortcut.ui + +import android.os.Bundle +import android.view.Gravity +import android.view.WindowManager +import com.android.systemui.statusbar.phone.DialogDelegate +import com.android.systemui.statusbar.phone.SystemUIDialog + +class ShortcutCustomizationDialogDelegate : DialogDelegate<SystemUIDialog> { + + override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { + super.onCreate(dialog, savedInstanceState) + dialog.window?.apply { setGravity(Gravity.CENTER) } + } + + override fun getWidth(dialog: SystemUIDialog): Int { + return WindowManager.LayoutParams.WRAP_CONTENT + } + + override fun getHeight(dialog: SystemUIDialog): Int { + return WindowManager.LayoutParams.WRAP_CONTENT + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt new file mode 100644 index 000000000000..02e206e09bc7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt @@ -0,0 +1,84 @@ +/* + * 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.ui + +import android.app.Dialog +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo +import com.android.systemui.keyboard.shortcut.ui.composable.AssignNewShortcutDialog +import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState +import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.statusbar.phone.SystemUIDialogFactory +import com.android.systemui.statusbar.phone.create +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +class ShortcutCustomizationDialogStarter +@AssistedInject +constructor( + viewModelFactory: ShortcutCustomizationViewModel.Factory, + private val dialogFactory: SystemUIDialogFactory, +) : ExclusiveActivatable() { + + private var dialog: Dialog? = null + private val viewModel = viewModelFactory.create() + + override suspend fun onActivated(): Nothing { + viewModel.shortcutCustomizationUiState.collect { uiState -> + if ( + uiState is ShortcutCustomizationUiState.AddShortcutDialog && + !uiState.isDialogShowing + ) { + dialog = createAddShortcutDialog().also { it.show() } + viewModel.onAddShortcutDialogShown() + } else if (uiState is ShortcutCustomizationUiState.Inactive) { + dialog?.dismiss() + dialog = null + } + } + } + + fun onAddShortcutDialogRequested(shortcutBeingCustomized: ShortcutInfo) { + viewModel.onAddShortcutDialogRequested(shortcutBeingCustomized) + } + + private fun createAddShortcutDialog(): Dialog { + return dialogFactory.create(dialogDelegate = ShortcutCustomizationDialogDelegate()) { dialog + -> + val uiState by viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle() + AssignNewShortcutDialog( + uiState = uiState, + modifier = Modifier.width(364.dp).wrapContentHeight().padding(vertical = 24.dp), + onKeyPress = { viewModel.onKeyPressed(it) }, + onCancel = { dialog.dismiss() }, + ) + dialog.setOnDismissListener { viewModel.onAddShortcutDialogDismissed() } + } + } + + @AssistedFactory + interface Factory { + fun create(): ShortcutCustomizationDialogStarter + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt index d33ab2acc8fb..807c70bbc225 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt @@ -24,6 +24,7 @@ import android.os.UserHandle import android.provider.Settings import androidx.annotation.VisibleForTesting import androidx.compose.foundation.layout.width +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -47,15 +48,18 @@ class ShortcutHelperDialogStarter @Inject constructor( @Application private val applicationScope: CoroutineScope, - private val viewModel: ShortcutHelperViewModel, + private val shortcutHelperViewModel: ShortcutHelperViewModel, + shortcutCustomizationDialogStarterFactory: ShortcutCustomizationDialogStarter.Factory, private val dialogFactory: SystemUIDialogFactory, private val activityStarter: ActivityStarter, ) : CoreStartable { @VisibleForTesting var dialog: Dialog? = null + private val shortcutCustomizationDialogStarter = + shortcutCustomizationDialogStarterFactory.create() override fun start() { - viewModel.shouldShow + shortcutHelperViewModel.shouldShow .map { shouldShow -> if (shouldShow) { dialog = createShortcutHelperDialog().also { it.show() } @@ -69,16 +73,21 @@ constructor( private fun createShortcutHelperDialog(): Dialog { return dialogFactory.createBottomSheet( content = { dialog -> - val shortcutsUiState by viewModel.shortcutsUiState.collectAsStateWithLifecycle() + val shortcutsUiState by + shortcutHelperViewModel.shortcutsUiState.collectAsStateWithLifecycle() + LaunchedEffect(Unit) { shortcutCustomizationDialogStarter.activate() } ShortcutHelper( modifier = Modifier.width(getWidth()), shortcutsUiState = shortcutsUiState, onKeyboardSettingsClicked = { onKeyboardSettingsClicked(dialog) }, - onSearchQueryChanged = { viewModel.onSearchQueryChanged(it) }, + onSearchQueryChanged = { shortcutHelperViewModel.onSearchQueryChanged(it) }, + onCustomizationRequested = { + shortcutCustomizationDialogStarter.onAddShortcutDialogRequested(it) + }, ) - dialog.setOnDismissListener { viewModel.onViewClosed() } + dialog.setOnDismissListener { shortcutHelperViewModel.onViewClosed() } }, - maxWidth = ShortcutHelperBottomSheet.LargeScreenWidthLandscape + maxWidth = ShortcutHelperBottomSheet.LargeScreenWidthLandscape, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt new file mode 100644 index 000000000000..43f0f200ab23 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt @@ -0,0 +1,272 @@ +/* + * 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.ui.composable + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +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.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.ErrorOutline +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey +import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState +import com.android.systemui.res.R + +@Composable +fun AssignNewShortcutDialog( + uiState: ShortcutCustomizationUiState, + modifier: Modifier = Modifier, + onKeyPress: (KeyEvent) -> Boolean, + onCancel: () -> Unit, +) { + if (uiState is ShortcutCustomizationUiState.AddShortcutDialog) { + Column(modifier = modifier) { + Title( + uiState.shortcutLabel, + modifier = Modifier.padding(horizontal = 24.dp).width(316.dp), + ) + Description( + modifier = Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp).width(316.dp) + ) + PromptShortcutModifier( + modifier = + Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp) + .width(131.dp) + .height(48.dp), + defaultModifierKey = uiState.defaultCustomShortcutModifierKey, + ) + SelectedKeyCombinationContainer( + shouldShowErrorMessage = uiState.shouldShowErrorMessage, + onKeyPress = onKeyPress, + ) + KeyCombinationAlreadyInUseErrorMessage(uiState.shouldShowErrorMessage) + DialogButtons(onCancel, isValidKeyCombination = uiState.isValidKeyCombination) + } + } +} + +@Composable +fun DialogButtons(onCancel: () -> Unit, isValidKeyCombination: Boolean) { + Row( + modifier = + Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp) + .sizeIn(minWidth = 316.dp, minHeight = 48.dp), + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.End, + ) { + ShortcutHelperButton( + shape = RoundedCornerShape(50.dp), + onClick = onCancel, + color = Color.Transparent, + width = 80.dp, + contentColor = MaterialTheme.colorScheme.primary, + text = stringResource(R.string.shortcut_helper_customize_dialog_cancel_button_label), + ) + Spacer(modifier = Modifier.width(8.dp)) + ShortcutHelperButton( + onClick = {}, + color = MaterialTheme.colorScheme.primary, + width = 116.dp, + contentColor = MaterialTheme.colorScheme.onPrimary, + text = + stringResource(R.string.shortcut_helper_customize_dialog_set_shortcut_button_label), + enabled = isValidKeyCombination, + ) + } +} + +@Composable +fun KeyCombinationAlreadyInUseErrorMessage(shouldShowErrorMessage: Boolean) { + if (shouldShowErrorMessage) { + Box(modifier = Modifier.padding(horizontal = 16.dp).width(332.dp).height(40.dp)) { + Text( + text = stringResource(R.string.shortcut_helper_customize_dialog_error_message), + style = MaterialTheme.typography.bodyMedium, + fontSize = 14.sp, + lineHeight = 20.sp, + fontWeight = FontWeight.W500, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(start = 24.dp).width(252.dp), + ) + } + } +} + +@Composable +fun SelectedKeyCombinationContainer( + keyCombination: String = + stringResource(R.string.shortcut_helper_add_shortcut_dialog_placeholder), + shouldShowErrorMessage: Boolean, + onKeyPress: (KeyEvent) -> Boolean, +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + val outlineColor = + if (!isFocused) MaterialTheme.colorScheme.outline + else if (shouldShowErrorMessage) MaterialTheme.colorScheme.error + else MaterialTheme.colorScheme.primary + + ClickableShortcutSurface( + onClick = {}, + color = Color.Transparent, + shape = RoundedCornerShape(50.dp), + modifier = + Modifier.padding(all = 16.dp) + .sizeIn(minWidth = 332.dp, minHeight = 56.dp) + .border(width = 2.dp, color = outlineColor, shape = RoundedCornerShape(50.dp)) + .onPreviewKeyEvent { onKeyPress(it) }, + interactionSource = interactionSource, + ) { + Row( + modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 16.dp, bottom = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = keyCombination, + style = MaterialTheme.typography.headlineSmall, + fontSize = 16.sp, + lineHeight = 24.sp, + fontWeight = FontWeight.W500, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.width(252.dp), + ) + Spacer(modifier = Modifier.weight(1f)) + if (shouldShowErrorMessage) { + Icon( + imageVector = Icons.Default.ErrorOutline, + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.error, + ) + } + } + } +} + +@Composable +private fun Title(title: String, modifier: Modifier = Modifier) { + Text( + text = title, + style = MaterialTheme.typography.headlineSmall, + fontSize = 24.sp, + modifier = modifier.wrapContentSize(Alignment.Center), + color = MaterialTheme.colorScheme.onSurface, + lineHeight = 32.sp, + ) +} + +@Composable +private fun Description(modifier: Modifier = Modifier) { + Text( + text = stringResource(id = R.string.shortcut_helper_customize_mode_sub_title), + style = MaterialTheme.typography.bodyMedium, + fontSize = 14.sp, + lineHeight = 20.sp, + modifier = modifier.wrapContentSize(Alignment.Center), + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) +} + +@Composable +private fun PromptShortcutModifier( + modifier: Modifier, + defaultModifierKey: ShortcutKey.Icon.ResIdIcon, +) { + Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(2.dp)) { + ActionKeyContainer(defaultModifierKey) + PlusIconContainer() + } +} + +@Composable +private fun ActionKeyContainer(defaultModifierKey: ShortcutKey.Icon.ResIdIcon) { + Row( + modifier = + Modifier.height(48.dp) + .width(105.dp) + .background( + color = MaterialTheme.colorScheme.surface, + shape = RoundedCornerShape(16.dp), + ) + .padding(all = 12.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + ActionKeyIcon(defaultModifierKey) + ActionKeyText() + } +} + +@Composable +fun ActionKeyText() { + Text( + text = "Action", + style = MaterialTheme.typography.titleMedium, + fontSize = 16.sp, + lineHeight = 24.sp, + modifier = Modifier.wrapContentSize(Alignment.Center), + color = MaterialTheme.colorScheme.onSurface, + ) +} + +@Composable +private fun ActionKeyIcon(defaultModifierKey: ShortcutKey.Icon.ResIdIcon) { + Icon( + painter = painterResource(id = defaultModifierKey.drawableResId), + contentDescription = stringResource(R.string.shortcut_helper_content_description_meta_key), + modifier = Modifier.size(24.dp).wrapContentSize(Alignment.Center), + ) +} + +@Composable +private fun PlusIconContainer() { + Icon( + tint = MaterialTheme.colorScheme.onSurface, + imageVector = Icons.Default.Add, + contentDescription = + stringResource(id = R.string.shortcut_helper_content_description_plus_icon), + modifier = Modifier.padding(vertical = 12.dp).size(24.dp).wrapContentSize(Alignment.Center), + ) +} 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 abddc7059ece..13934ea38233 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 @@ -110,6 +110,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutM 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.ShortcutIcon +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import com.android.systemui.keyboard.shortcut.ui.model.IconSource @@ -124,6 +125,7 @@ fun ShortcutHelper( modifier: Modifier = Modifier, shortcutsUiState: ShortcutsUiState, useSinglePane: @Composable () -> Boolean = { shouldUseSinglePane() }, + onCustomizationRequested: (ShortcutInfo) -> Unit = {}, ) { when (shortcutsUiState) { is ShortcutsUiState.Active -> { @@ -133,6 +135,7 @@ fun ShortcutHelper( onSearchQueryChanged, modifier, onKeyboardSettingsClicked, + onCustomizationRequested, ) } else -> { @@ -148,6 +151,7 @@ private fun ActiveShortcutHelper( onSearchQueryChanged: (String) -> Unit, modifier: Modifier, onKeyboardSettingsClicked: () -> Unit, + onCustomizationRequested: (ShortcutInfo) -> Unit = {}, ) { var selectedCategoryType by remember(shortcutsUiState.defaultSelectedCategory) { @@ -173,6 +177,7 @@ private fun ActiveShortcutHelper( onCategorySelected = { selectedCategoryType = it }, onKeyboardSettingsClicked, shortcutsUiState.isShortcutCustomizerFlagEnabled, + onCustomizationRequested, ) } } @@ -362,6 +367,7 @@ private fun ShortcutHelperTwoPane( onCategorySelected: (ShortcutCategoryType?) -> Unit, onKeyboardSettingsClicked: () -> Unit, isShortcutCustomizerFlagEnabled: Boolean, + onCustomizationRequested: (ShortcutInfo) -> Unit = {}, ) { val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType } var isCustomizeModeEntered by remember { mutableStateOf(false) } @@ -400,6 +406,7 @@ private fun ShortcutHelperTwoPane( Modifier.fillMaxSize().padding(top = 8.dp), selectedCategory, isCustomizing = isCustomizing, + onCustomizationRequested = onCustomizationRequested, ) } } @@ -434,6 +441,7 @@ private fun EndSidePanel( modifier: Modifier, category: ShortcutCategoryUi?, isCustomizing: Boolean, + onCustomizationRequested: (ShortcutInfo) -> Unit = {}, ) { val listState = rememberLazyListState() LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) } @@ -447,6 +455,15 @@ private fun EndSidePanel( searchQuery = searchQuery, subCategory = subcategory, isCustomizing = isCustomizing, + onCustomizationRequested = { label, subCategoryLabel -> + onCustomizationRequested( + ShortcutInfo( + label = label, + subCategoryLabel = subCategoryLabel, + categoryType = category.type, + ) + ) + }, ) Spacer(modifier = Modifier.height(8.dp)) } @@ -476,6 +493,7 @@ private fun SubCategoryContainerDualPane( searchQuery: String, subCategory: ShortcutSubCategory, isCustomizing: Boolean, + onCustomizationRequested: (String, String) -> Unit = { _: String, _: String -> }, ) { Surface( modifier = Modifier.fillMaxWidth(), @@ -497,6 +515,7 @@ private fun SubCategoryContainerDualPane( searchQuery = searchQuery, shortcut = shortcut, isCustomizing = isCustomizing, + onCustomizationRequested = { onCustomizationRequested(it, subCategory.label) }, ) } } @@ -518,6 +537,7 @@ private fun Shortcut( searchQuery: String, shortcut: ShortcutModel, isCustomizing: Boolean = false, + onCustomizationRequested: (String) -> Unit = {}, ) { val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() @@ -541,7 +561,12 @@ private fun Shortcut( ShortcutDescriptionText(searchQuery = searchQuery, shortcut = shortcut) } Spacer(modifier = Modifier.width(24.dp)) - ShortcutKeyCombinations(modifier = Modifier.weight(1f), shortcut = shortcut, isCustomizing) + ShortcutKeyCombinations( + modifier = Modifier.weight(1f), + shortcut = shortcut, + isCustomizing = isCustomizing, + onAddShortcutClicked = { onCustomizationRequested(shortcut.label) }, + ) } } @@ -569,6 +594,7 @@ private fun ShortcutKeyCombinations( modifier: Modifier = Modifier, shortcut: ShortcutModel, isCustomizing: Boolean = false, + onAddShortcutClicked: () -> Unit = {}, ) { FlowRow( modifier = modifier, @@ -590,7 +616,7 @@ private fun ShortcutKeyCombinations( color = MaterialTheme.colorScheme.outline, shape = CircleShape, ), - onClick = {}, + onClick = { onAddShortcutClicked() }, color = Color.Transparent, width = 32.dp, height = 32.dp, diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt index 435968ee79ca..e761c7313ff3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt @@ -44,6 +44,7 @@ import androidx.compose.material3.LocalAbsoluteTonalElevation import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTonalElevationEnabled import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.contentColorFor import androidx.compose.material3.minimumInteractiveComponentSize @@ -283,6 +284,108 @@ fun ShortcutHelperButton( } @Composable +fun ShortcutHelperButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + shape: Shape = RoundedCornerShape(360.dp), + color: Color, + width: Dp, + height: Dp = 40.dp, + iconSource: IconSource = IconSource(), + text: String? = null, + contentColor: Color, + contentPaddingHorizontal: Dp = 16.dp, + contentPaddingVertical: Dp = 10.dp, + enabled: Boolean = true, +) { + ShortcutHelperButtonSurface( + onClick = onClick, + shape = shape, + color = color, + modifier = modifier, + enabled = enabled, + width = width, + height = height, + ) { + Row( + modifier = + Modifier.padding( + horizontal = contentPaddingHorizontal, + vertical = contentPaddingVertical, + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + if (iconSource.imageVector != null) { + Icon( + tint = contentColor, + imageVector = iconSource.imageVector, + contentDescription = + null, // TODO this probably should not be null for accessibility. + modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center), + ) + } + + if (iconSource.imageVector != null && text != null) + Spacer(modifier = Modifier.weight(1f)) + + if (text != null) { + Text( + text, + color = contentColor, + fontSize = 14.sp, + style = MaterialTheme.typography.labelLarge, + modifier = Modifier.wrapContentSize(Alignment.Center), + ) + } + } + } +} + +@Composable +fun ShortcutHelperButtonSurface( + onClick: () -> Unit, + shape: Shape, + color: Color, + modifier: Modifier = Modifier, + enabled: Boolean, + width: Dp, + height: Dp, + content: @Composable () -> Unit, +) { + if (enabled) { + ClickableShortcutSurface( + onClick = onClick, + shape = shape, + color = color, + modifier = modifier.semantics { role = Role.Button }.width(width).height(height), + interactionsConfig = + InteractionsConfig( + hoverOverlayColor = MaterialTheme.colorScheme.onSurface, + hoverOverlayAlpha = 0.11f, + pressedOverlayColor = MaterialTheme.colorScheme.onSurface, + pressedOverlayAlpha = 0.15f, + focusOutlineColor = MaterialTheme.colorScheme.secondary, + focusOutlineStrokeWidth = 3.dp, + focusOutlinePadding = 2.dp, + surfaceCornerRadius = 28.dp, + focusOutlineCornerRadius = 33.dp, + ), + ) { + content() + } + } else { + Surface( + shape = shape, + color = color.copy(0.38f), + modifier = modifier.semantics { role = Role.Button }.width(width).height(height), + ) { + content() + } + } +} + +@Composable private fun surfaceColorAtElevation(color: Color, elevation: Dp): Color { return MaterialTheme.colorScheme.applyTonalElevation(color, elevation) } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt new file mode 100644 index 000000000000..e9f2a3b8e5b3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.ui.model + +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey + +sealed interface ShortcutCustomizationUiState { + data class AddShortcutDialog( + val shortcutLabel: String, + val shouldShowErrorMessage: Boolean, + val isValidKeyCombination: Boolean, + val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon, + val isDialogShowing: Boolean, + ) : ShortcutCustomizationUiState + + data object Inactive : ShortcutCustomizationUiState +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt new file mode 100644 index 000000000000..b9253878ba0d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.ui.viewmodel + +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.input.key.KeyEvent +import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo +import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update + +class ShortcutCustomizationViewModel +@AssistedInject +constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationInteractor) { + private val _shortcutBeingCustomized = mutableStateOf<ShortcutInfo?>(null) + + private val _shortcutCustomizationUiState = + MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive) + + val shortcutCustomizationUiState = _shortcutCustomizationUiState.asStateFlow() + + fun onAddShortcutDialogRequested(shortcutBeingCustomized: ShortcutInfo) { + _shortcutCustomizationUiState.value = + ShortcutCustomizationUiState.AddShortcutDialog( + shortcutLabel = shortcutBeingCustomized.label, + shouldShowErrorMessage = false, + isValidKeyCombination = false, + defaultCustomShortcutModifierKey = + shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(), + isDialogShowing = false, + ) + + _shortcutBeingCustomized.value = shortcutBeingCustomized + } + + fun onAddShortcutDialogShown() { + _shortcutCustomizationUiState.update { uiState -> + (uiState as? ShortcutCustomizationUiState.AddShortcutDialog) + ?.let { it.copy(isDialogShowing = true) } + ?: uiState + } + } + + fun onAddShortcutDialogDismissed() { + _shortcutBeingCustomized.value = null + _shortcutCustomizationUiState.value = ShortcutCustomizationUiState.Inactive + } + + fun onKeyPressed(keyEvent: KeyEvent): Boolean { + // TODO Not yet implemented b/373638584 + return false + } + + @AssistedFactory + interface Factory { + fun create(): ShortcutCustomizationViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt index 585f7edeb0f2..922bc15c0633 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt @@ -52,6 +52,7 @@ class StickyKeysRepositoryImpl constructor( private val inputManager: InputManager, @Background private val backgroundDispatcher: CoroutineDispatcher, + // TODO: b/377244768 - Change to inject SecureSettingsRepository secureSettingsRepository: UserAwareSecureSettingsRepository, private val stickyKeysLogger: StickyKeysLogger, ) : StickyKeysRepository { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 53177de89733..80ac2fcd4aa4 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -696,7 +696,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack TaskStackChangeListeners.getInstance().unregisterTaskStackListener( mTaskStackListener); DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); - mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(null)); + mPipOptional.ifPresent(pip -> pip.removeOnIsInPipStateChangedListener( + mOnIsInPipStateChangedListener)); try { mWindowManagerService.unregisterSystemGestureExclusionListener( @@ -720,7 +721,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack mTaskStackListener); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mUiThreadContext.getExecutor()::execute, mOnPropertiesChangedListener); - mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener( + mPipOptional.ifPresent(pip -> pip.addOnIsInPipStateChangedListener( mOnIsInPipStateChangedListener)); mDesktopModeOptional.ifPresent( dm -> dm.addDesktopGestureExclusionRegionListener( @@ -1191,11 +1192,13 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } private void pilferPointers() { - // Capture inputs - mInputMonitor.pilferPointers(); - // Notify FalsingManager that an intentional gesture has occurred. - mFalsingManager.isFalseTouch(BACK_GESTURE); - mInputEventReceiver.setBatchingEnabled(true); + if (mInputMonitor != null) { + // Capture inputs + mInputMonitor.pilferPointers(); + // Notify FalsingManager that an intentional gesture has occurred. + mFalsingManager.isFalseTouch(BACK_GESTURE); + mInputEventReceiver.setBatchingEnabled(true); + } } private boolean isButtonPressFromTrackpad(MotionEvent ev) { diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java index 765b45bdbf2e..bab88c0b0bf9 100644 --- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java +++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java @@ -159,7 +159,7 @@ public class QRCodeScannerController implements * Returns true if lock screen entry point for QR Code Scanner is to be enabled. */ public boolean isEnabledForLockScreenButton() { - return mQRCodeScannerEnabled && isAbleToLaunchScannerActivity() && isAllowedOnLockScreen(); + return isAbleToLaunchScannerActivity() && isAllowedOnLockScreen(); } /** Returns whether the QR scanner button is allowed on lockscreen. */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index a1071907cd3d..2a5ffc6cc391 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -27,6 +27,7 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.content.pm.UserInfo +import android.content.res.Resources import android.graphics.drawable.Drawable import android.os.IBinder import android.os.PowerExemptionManager @@ -54,6 +55,7 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.Dumpable +import com.android.systemui.Flags; import com.android.systemui.res.R import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator @@ -137,7 +139,7 @@ interface FgsManagerController { @SysUISingleton class FgsManagerControllerImpl @Inject constructor( - private val context: Context, + @Main private val resources: Resources, @Main private val mainExecutor: Executor, @Background private val backgroundExecutor: Executor, private val systemClock: SystemClock, @@ -223,6 +225,14 @@ class FgsManagerControllerImpl @Inject constructor( private val userVisibleJobObserver = UserVisibleJobObserver() + private val stoppableApps by lazy { resources + .getStringArray(com.android.internal.R.array.stoppable_fgs_system_apps) + } + + private val vendorStoppableApps by lazy { resources + .getStringArray(com.android.internal.R.array.vendor_stoppable_fgs_system_apps) + } + override fun init() { synchronized(lock) { if (initialized) { @@ -725,9 +735,22 @@ class FgsManagerControllerImpl @Inject constructor( } else -> UIControl.NORMAL } + // If the app wants to be a good citizen by being stoppable, even if the category it + // belongs to is exempted for background restriction, let it be stoppable by user. + if (Flags.stoppableFgsSystemApp()) { + if (isStoppableApp(packageName)) { + uiControl = UIControl.NORMAL + } + } + uiControlInitialized = true } + fun isStoppableApp(packageName: String): Boolean { + return stoppableApps.contains(packageName) || + vendorStoppableApps.contains(packageName) + } + override fun equals(other: Any?): Boolean { if (other !is UserPackage) { return false 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 624cf306a3b2..e912a0c7faa6 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 @@ -63,6 +63,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository import com.android.systemui.util.LargeScreenUtils import com.android.systemui.util.asIndenting +import com.android.systemui.util.kotlin.emitOnStart import com.android.systemui.util.printSection import com.android.systemui.util.println import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow @@ -278,6 +279,24 @@ constructor( private val mediaSuddenlyAppearingInLandscape: Boolean get() = !qqsMediaInRow && qsMediaInRow + private val collapsedLandscapeMedia by + hydrator.hydratedStateOf( + traceName = "collapsedLandscapeMedia", + initialValue = resources.getBoolean(R.bool.config_quickSettingsMediaLandscapeCollapsed), + source = + configurationInteractor.onAnyConfigurationChange.emitOnStart().map { + resources.getBoolean(R.bool.config_quickSettingsMediaLandscapeCollapsed) + }, + ) + + private val qqsMediaExpansion: Float + get() = + if (qqsMediaInRow && collapsedLandscapeMedia) { + MediaHostState.COLLAPSED + } else { + MediaHostState.EXPANDED + } + private var qsBounds by mutableStateOf(Rect()) private val constrainedSquishinessFraction: Float @@ -379,6 +398,7 @@ constructor( initMediaHosts() // init regardless of using media (same as current QS). coroutineScope { launch { hydrateSquishinessInteractor() } + launch { hydrateQqsMediaExpansion() } launch { hydrator.activate() } launch { containerViewModel.activate() } launch { qqsMediaInRowViewModel.activate() } @@ -389,7 +409,7 @@ constructor( private fun initMediaHosts() { qqsMediaHost.apply { - expansion = MediaHostState.EXPANDED + expansion = qqsMediaExpansion showsOnlyActiveMedia = true init(MediaHierarchyManager.LOCATION_QQS) } @@ -405,6 +425,10 @@ constructor( .collect { squishinessInteractor.setSquishinessValue(it) } } + private suspend fun hydrateQqsMediaExpansion() { + snapshotFlow { qqsMediaExpansion }.collect { qqsMediaHost.expansion = it } + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.asIndenting().run { printSection("Quick Settings state") { @@ -448,6 +472,8 @@ constructor( println("qqsMediaInRow", qqsMediaInRow) println("qsMediaVisible", qsMediaVisible) println("qsMediaInRow", qsMediaInRow) + println("collapsedLandscapeMedia", collapsedLandscapeMedia) + println("qqsMediaExpansion", qqsMediaExpansion) } } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index ce9c441654bf..a5eb92b10239 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -24,7 +24,10 @@ import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; +import static android.window.BackEvent.EDGE_NONE; +import static com.android.window.flags.Flags.predictiveBackSwipeEdgeNoneApi; +import static com.android.window.flags.Flags.predictiveBackThreeButtonNav; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER; @@ -41,6 +44,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_V import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION; import android.annotation.FloatRange; +import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -114,6 +118,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder; +import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInterface; @@ -174,6 +179,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private Region mActiveNavBarRegion; private final BroadcastDispatcher mBroadcastDispatcher; + private final BackAnimation mBackAnimation; private IOverviewProxy mOverviewProxy; private int mConnectionBackoffAttempts; @@ -287,11 +293,18 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } @Override - public void onBackPressed() { - verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> { - sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); - sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); - }); + public void onBackEvent(@Nullable KeyEvent keyEvent) throws RemoteException { + if (predictiveBackThreeButtonNav() && predictiveBackSwipeEdgeNoneApi() + && mBackAnimation != null && keyEvent != null) { + mBackAnimation.setTriggerBack(!keyEvent.isCanceled()); + mBackAnimation.onBackMotion(/* touchX */ 0, /* touchY */ 0, keyEvent.getAction(), + EDGE_NONE); + } else { + verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> { + sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); + sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); + }); + } } @Override @@ -657,7 +670,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis AssistUtils assistUtils, DumpManager dumpManager, Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder, - BroadcastDispatcher broadcastDispatcher + BroadcastDispatcher broadcastDispatcher, + Optional<BackAnimation> backAnimation ) { // b/241601880: This component should only be running for primary users or // secondaryUsers when visibleBackgroundUsers are supported. @@ -695,6 +709,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mDisplayTracker = displayTracker; mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder; mBroadcastDispatcher = broadcastDispatcher; + mBackAnimation = backAnimation.orElse(null); if (!KeyguardWmStateRefactor.isEnabled()) { mSysuiUnlockAnimationController = sysuiUnlockAnimationController; diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt index 2ef27a8df117..60ed2de5c532 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt @@ -17,7 +17,7 @@ package com.android.systemui.settings import android.hardware.display.DisplayManager -import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS +import android.hardware.display.DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS import android.os.Handler import android.view.Display import androidx.annotation.GuardedBy @@ -32,7 +32,7 @@ import java.util.concurrent.Executor class DisplayTrackerImpl internal constructor( val displayManager: DisplayManager, - @Background val backgroundHandler: Handler + @Background val backgroundHandler: Handler, ) : DisplayTracker { override val defaultDisplayId: Int = Display.DEFAULT_DISPLAY override val allDisplays: Array<Display> @@ -47,27 +47,21 @@ internal constructor( val displayChangedListener: DisplayManager.DisplayListener = object : DisplayManager.DisplayListener { override fun onDisplayAdded(displayId: Int) { - traceSection( - "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayAdded", - ) { + traceSection("DisplayTrackerImpl.displayChangedDisplayListener#onDisplayAdded") { val list = synchronized(displayCallbacks) { displayCallbacks.toList() } onDisplayAdded(displayId, list) } } override fun onDisplayRemoved(displayId: Int) { - traceSection( - "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayRemoved", - ) { + traceSection("DisplayTrackerImpl.displayChangedDisplayListener#onDisplayRemoved") { val list = synchronized(displayCallbacks) { displayCallbacks.toList() } onDisplayRemoved(displayId, list) } } override fun onDisplayChanged(displayId: Int) { - traceSection( - "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayChanged", - ) { + traceSection("DisplayTrackerImpl.displayChangedDisplayListener#onDisplayChanged") { val list = synchronized(displayCallbacks) { displayCallbacks.toList() } onDisplayChanged(displayId, list) } @@ -83,7 +77,7 @@ internal constructor( override fun onDisplayChanged(displayId: Int) { traceSection( - "DisplayTrackerImpl.displayBrightnessChangedDisplayListener#onDisplayChanged", + "DisplayTrackerImpl.displayBrightnessChangedDisplayListener#onDisplayChanged" ) { val list = synchronized(brightnessCallbacks) { brightnessCallbacks.toList() } onDisplayChanged(displayId, list) @@ -102,14 +96,15 @@ internal constructor( override fun addBrightnessChangeCallback( callback: DisplayTracker.Callback, - executor: Executor + executor: Executor, ) { synchronized(brightnessCallbacks) { if (brightnessCallbacks.isEmpty()) { displayManager.registerDisplayListener( displayBrightnessChangedListener, backgroundHandler, - EVENT_FLAG_DISPLAY_BRIGHTNESS + /* eventFlags */ 0, + PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS, ) } brightnessCallbacks.add(DisplayTrackerDataItem(WeakReference(callback), executor)) @@ -159,7 +154,7 @@ internal constructor( private inline fun notifySubscribers( crossinline action: DisplayTracker.Callback.() -> Unit, - list: List<DisplayTrackerDataItem> + list: List<DisplayTrackerDataItem>, ) { list.forEach { if (it.callback.get() != null) { @@ -170,7 +165,7 @@ internal constructor( private data class DisplayTrackerDataItem( val callback: WeakReference<DisplayTracker.Callback>, - val executor: Executor + val executor: Executor, ) { fun sameOrEmpty(other: DisplayTracker.Callback): Boolean { return callback.get()?.equals(other) ?: true diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index 649f8db89bc0..90d27f4b33e9 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -44,6 +44,7 @@ import android.util.MathUtils; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -56,6 +57,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; import com.android.systemui.log.core.LogMessage; +import com.android.systemui.res.R; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.SecureSettings; @@ -111,6 +113,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig private boolean mControlValueInitialized; private float mBrightnessMin = PowerManager.BRIGHTNESS_MIN; private float mBrightnessMax = PowerManager.BRIGHTNESS_MAX; + private boolean mIsBrightnessOverriddenByWindow = false; private ValueAnimator mSliderAnimator; @@ -246,12 +249,14 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig @Override public void run() { final boolean inVrMode = mIsVrModeEnabled; - final BrightnessInfo info = mContext.getDisplay().getBrightnessInfo(); + final BrightnessInfo info = getBrightnessInfo(); if (info == null) { return; } mBrightnessMax = info.brightnessMaximum; mBrightnessMin = info.brightnessMinimum; + mIsBrightnessOverriddenByWindow = info.isBrightnessOverrideByWindow; + // Value is passed as intbits, since this is what the message takes. final int valueAsIntBits = Float.floatToIntBits(info.brightness); mMainHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits, @@ -353,7 +358,19 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig public void onChanged(boolean tracking, int value, boolean stopTracking) { boolean starting = !mTrackingTouch && tracking; mTrackingTouch = tracking; - if (mExternalChange) return; + if (starting) { + if (Flags.showToastWhenAppControlBrightness()) { + // Showing the warning toast if the current running app window has + // controlled the brightness value. + if (mIsBrightnessOverriddenByWindow) { + mControl.showToast(R.string.quick_settings_brightness_unable_adjust_msg); + } + } + } + if (mExternalChange + || (Flags.showToastWhenAppControlBrightness() && mIsBrightnessOverriddenByWindow)) { + return; + } if (mSliderAnimator != null) { mSliderAnimator.cancel(); @@ -424,6 +441,11 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig mDisplayManager.setTemporaryBrightness(mDisplayId, brightness); } + @VisibleForTesting + BrightnessInfo getBrightnessInfo() { + return mContext.getDisplay().getBrightnessInfo(); + } + private void updateVrMode(boolean isEnabled) { if (mIsVrModeEnabled != isEnabled) { mIsVrModeEnabled = isEnabled; diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java index 2f7df21893b7..3a90d2b9df7b 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java @@ -16,6 +16,7 @@ package com.android.systemui.settings.brightness; +import android.annotation.StringRes; import android.content.Context; import android.content.Intent; import android.view.LayoutInflater; @@ -36,6 +37,7 @@ import com.android.systemui.haptics.slider.HapticSliderViewBinder; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.res.R; +import com.android.systemui.settings.brightness.ui.BrightnessWarningToast; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.util.ViewController; @@ -68,6 +70,8 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV private final HapticSliderPlugin mBrightnessSliderHapticPlugin; private final ActivityStarter mActivityStarter; + private final BrightnessWarningToast mBrightnessWarningToast; + private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { @@ -90,12 +94,14 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV FalsingManager falsingManager, UiEventLogger uiEventLogger, HapticSliderPlugin brightnessSliderHapticPlugin, - ActivityStarter activityStarter) { + ActivityStarter activityStarter, + BrightnessWarningToast brightnessWarningToast) { super(brightnessSliderView); mFalsingManager = falsingManager; mUiEventLogger = uiEventLogger; mBrightnessSliderHapticPlugin = brightnessSliderHapticPlugin; mActivityStarter = activityStarter; + mBrightnessWarningToast = brightnessWarningToast; } /** @@ -225,6 +231,15 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV } @Override + public void showToast(@StringRes int resId) { + if (mBrightnessWarningToast.isToastActive()) { + return; + } + mBrightnessWarningToast.show(mView.getContext(), + R.string.quick_settings_brightness_unable_adjust_msg); + } + + @Override public boolean isVisible() { // this should be called rarely - once or twice per slider's value change, but not for // every value change when user slides finger - only the final one. @@ -286,6 +301,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV private final SystemClock mSystemClock; private final ActivityStarter mActivityStarter; private final MSDLPlayer mMSDLPlayer; + private final BrightnessWarningToast mBrightnessWarningToast; @Inject public Factory( @@ -294,7 +310,8 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV VibratorHelper vibratorHelper, MSDLPlayer msdlPlayer, SystemClock clock, - ActivityStarter activityStarter + ActivityStarter activityStarter, + BrightnessWarningToast brightnessWarningToast ) { mFalsingManager = falsingManager; mUiEventLogger = uiEventLogger; @@ -302,6 +319,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV mSystemClock = clock; mActivityStarter = activityStarter; mMSDLPlayer = msdlPlayer; + mBrightnessWarningToast = brightnessWarningToast; } /** @@ -323,8 +341,8 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV mSystemClock, new HapticSlider.SeekBar(root.requireViewById(R.id.slider))); HapticSliderViewBinder.bind(viewRoot, plugin); - return new BrightnessSliderController( - root, mFalsingManager, mUiEventLogger, plugin, mActivityStarter); + return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger, plugin, + mActivityStarter, mBrightnessWarningToast); } /** Get the layout to inflate based on what slider to use */ diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java index 24bc67047a47..ed69d35e6053 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java @@ -16,6 +16,7 @@ package com.android.systemui.settings.brightness; +import android.annotation.StringRes; import android.view.MotionEvent; import com.android.settingslib.RestrictedLockUtils; @@ -37,5 +38,6 @@ public interface ToggleSlider { void showView(); void hideView(); + void showToast(@StringRes int resId); boolean isVisible(); } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt new file mode 100644 index 000000000000..dfbdaa62ec44 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.settings.brightness.ui + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.annotation.StringRes +import android.content.Context +import android.graphics.PixelFormat +import android.view.Gravity +import android.view.View +import android.view.WindowManager +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.toast.ToastFactory +import javax.inject.Inject + +@SysUISingleton +class BrightnessWarningToast +@Inject +constructor( + private val toastFactory: ToastFactory, + private val windowManager: WindowManager, +) { + private var toastView: View? = null + + fun show(viewContext: Context, @StringRes resId: Int) { + val res = viewContext.resources + // Show the brightness warning toast with passing the toast inflation required context, + // userId and resId from SystemUI package. + val systemUIToast = toastFactory.createToast( + viewContext, + res.getString(resId), viewContext.packageName, viewContext.getUserId(), + res.configuration.orientation + ) + if (systemUIToast == null) { + return + } + + toastView = systemUIToast.view + + val params = WindowManager.LayoutParams() + params.height = WindowManager.LayoutParams.WRAP_CONTENT + params.width = WindowManager.LayoutParams.WRAP_CONTENT + params.format = PixelFormat.TRANSLUCENT + params.title = "Brightness warning toast" + params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL + params.flags = (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) + params.y = systemUIToast.yOffset + + val absGravity = Gravity.getAbsoluteGravity( + systemUIToast.gravity, + res.configuration.layoutDirection + ) + params.gravity = absGravity + if ((absGravity and Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { + params.horizontalWeight = TOAST_PARAMS_HORIZONTAL_WEIGHT + } + if ((absGravity and Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { + params.verticalWeight = TOAST_PARAMS_VERTICAL_WEIGHT + } + + windowManager.addView(toastView, params) + + val inAnimator = systemUIToast.inAnimation + inAnimator?.start() + + toastView!!.postDelayed({ + val outAnimator = systemUIToast.outAnimation + if (outAnimator != null) { + outAnimator.start() + outAnimator.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animator: Animator) { + windowManager.removeViewImmediate(toastView) + toastView = null + } + }) + } + }, TOAST_DURATION_MS) + } + + fun isToastActive(): Boolean { + return toastView != null && toastView!!.isAttachedToWindow + } + + companion object { + private const val TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f + private const val TOAST_PARAMS_VERTICAL_WEIGHT = 1.0f + private const val TOAST_DURATION_MS: Long = 3000 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 8c5a711d6a75..a5595edcbb95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -116,6 +116,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.user.domain.interactor.UserLogoutInteractor; import com.android.systemui.util.AlarmTimeout; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.wakelock.SettableWakeLock; @@ -162,6 +163,7 @@ public class KeyguardIndicationController { private final KeyguardLogger mKeyguardLogger; private final UserTracker mUserTracker; private final BouncerMessageInteractor mBouncerMessageInteractor; + private ViewGroup mIndicationArea; private KeyguardIndicationTextView mTopIndicationView; private KeyguardIndicationTextView mLockScreenIndicationView; @@ -187,6 +189,7 @@ public class KeyguardIndicationController { private final BiometricMessageInteractor mBiometricMessageInteractor; private DeviceEntryFingerprintAuthInteractor mDeviceEntryFingerprintAuthInteractor; private DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor; + private final UserLogoutInteractor mUserLogoutInteractor; private String mPersistentUnlockMessage; private String mAlignmentIndication; private boolean mForceIsDismissible; @@ -237,6 +240,13 @@ public class KeyguardIndicationController { showTrustAgentErrorMessage(mTrustAgentErrorMessage); } }; + @VisibleForTesting + final Consumer<Boolean> mIsLogoutEnabledCallback = + (Boolean isLogoutEnabled) -> { + if (mVisible) { + updateDeviceEntryIndication(false); + } + }; private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override public void onScreenTurnedOn() { @@ -299,7 +309,8 @@ public class KeyguardIndicationController { KeyguardInteractor keyguardInteractor, BiometricMessageInteractor biometricMessageInteractor, DeviceEntryFingerprintAuthInteractor deviceEntryFingerprintAuthInteractor, - DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor + DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor, + UserLogoutInteractor userLogoutInteractor ) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; @@ -331,6 +342,8 @@ public class KeyguardIndicationController { mBiometricMessageInteractor = biometricMessageInteractor; mDeviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor; mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor; + mUserLogoutInteractor = userLogoutInteractor; + mFaceAcquiredMessageDeferral = faceHelpMessageDeferral.create(); @@ -418,6 +431,9 @@ public class KeyguardIndicationController { mCoExAcquisitionMsgIdsToShowCallback); collectFlow(mIndicationArea, mDeviceEntryFingerprintAuthInteractor.isEngaged(), mIsFingerprintEngagedCallback); + collectFlow(mIndicationArea, + mUserLogoutInteractor.isLogoutEnabled(), + mIsLogoutEnabledCallback); } /** @@ -744,9 +760,7 @@ public class KeyguardIndicationController { } private void updateLockScreenLogoutView() { - final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled() - && getCurrentUser() != UserHandle.USER_SYSTEM; - if (shouldShowLogout) { + if (mUserLogoutInteractor.isLogoutEnabled().getValue()) { mRotateTextViewController.updateIndication( INDICATION_TYPE_LOGOUT, new KeyguardIndication.Builder() @@ -760,7 +774,7 @@ public class KeyguardIndicationController { if (mFalsingManager.isFalseTap(LOW_PENALTY)) { return; } - mDevicePolicyManager.logoutUser(); + mUserLogoutInteractor.logOut(); }) .build(), false); @@ -1515,13 +1529,6 @@ public class KeyguardIndicationController { } @Override - public void onLogoutEnabledChanged() { - if (mVisible) { - updateDeviceEntryIndication(false); - } - } - - @Override public void onRequireUnlockForNfc() { showTransientIndication(mContext.getString(R.string.require_unlock_for_nfc)); hideTransientIndicationDelayed(DEFAULT_HIDE_DELAY_MS); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java index 3a24ec9408ad..c1b8d9d123b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java @@ -383,34 +383,20 @@ public class NotificationGroupingUtil { } protected boolean hasSameIcon(Object parentData, Object childData) { - Icon parentIcon = getIcon((Notification) parentData); - Icon childIcon = getIcon((Notification) childData); + Icon parentIcon = ((Notification) parentData).getSmallIcon(); + Icon childIcon = ((Notification) childData).getSmallIcon(); return parentIcon.sameAs(childIcon); } - private static Icon getIcon(Notification notification) { - if (notification.shouldUseAppIcon()) { - return notification.getAppIcon(); - } - return notification.getSmallIcon(); - } - /** * @return whether two ImageViews have the same colorFilterSet or none at all */ protected boolean hasSameColor(Object parentData, Object childData) { - int parentColor = getColor((Notification) parentData); - int childColor = getColor((Notification) childData); + int parentColor = ((Notification) parentData).color; + int childColor = ((Notification) childData).color; return parentColor == childColor; } - private static int getColor(Notification notification) { - if (notification.shouldUseAppIcon()) { - return 0; // the color filter isn't applied if using the app icon - } - return notification.color; - } - @Override public boolean isEmpty(View view) { return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index ad3afd4d1756..33f0c64269cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -27,7 +27,6 @@ import android.app.ActivityManager; import android.app.Notification; import android.content.Context; import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; @@ -36,7 +35,6 @@ import android.graphics.Color; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.Rect; -import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Trace; @@ -520,34 +518,8 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi userId = UserHandle.USER_SYSTEM; } - // Try to load the monochrome app icon if applicable - Drawable icon = maybeGetMonochromeAppIcon(context, statusBarIcon); - // Otherwise, just use the icon normally - if (icon == null) { - icon = statusBarIcon.icon.loadDrawableAsUser(context, userId); - } - return icon; - } - } - - @Nullable - private Drawable maybeGetMonochromeAppIcon(Context context, - StatusBarIcon statusBarIcon) { - if (android.app.Flags.notificationsUseMonochromeAppIcon() - && statusBarIcon.type == StatusBarIcon.Type.MaybeMonochromeAppIcon) { - // Check if we have a monochrome app icon - PackageManager pm = context.getPackageManager(); - Drawable appIcon = context.getApplicationInfo().loadIcon(pm); - if (appIcon instanceof AdaptiveIconDrawable) { - Drawable monochrome = ((AdaptiveIconDrawable) appIcon).getMonochrome(); - if (monochrome != null) { - setCropToPadding(true); - setScaleType(ScaleType.CENTER); - return new ScalingDrawableWrapper(monochrome, APP_ICON_SCALE); - } - } + return statusBarIcon.icon.loadDrawableAsUser(context, userId); } - return null; } public StatusBarIcon getStatusBarIcon() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt index 9c53cc13f702..e3dc70af5fe6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.chips.screenrecord.domain.interactor +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer @@ -28,14 +29,19 @@ import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn -import com.android.app.tracing.coroutines.launchTraced as launch +import kotlinx.coroutines.flow.transformLatest +import kotlinx.coroutines.launch /** Interactor for the screen recording chip shown in the status bar. */ @SysUISingleton +@OptIn(ExperimentalCoroutinesApi::class) class ScreenRecordChipInteractor @Inject constructor( @@ -44,6 +50,32 @@ constructor( private val mediaProjectionRepository: MediaProjectionRepository, @StatusBarChipsLog private val logger: LogBuffer, ) { + /** + * Emits true if we should assume that we're currently screen recording, even if + * [ScreenRecordRepository.screenRecordState] hasn't emitted [ScreenRecordModel.Recording] yet. + */ + private val shouldAssumeIsRecording: Flow<Boolean> = + screenRecordRepository.screenRecordState + .transformLatest { + when (it) { + is ScreenRecordModel.DoingNothing -> { + emit(false) + } + is ScreenRecordModel.Starting -> { + // If we're told that the recording will start in [it.millisUntilStarted], + // optimistically assume the recording did indeed start after that time even + // if [ScreenRecordRepository.screenRecordState] hasn't emitted + // [ScreenRecordModel.Recording] yet. Start 50ms early so that the chip + // timer will definitely be showing by the time the recording actually + // starts - see b/366448907. + delay(it.millisUntilStarted - 50) + emit(true) + } + is ScreenRecordModel.Recording -> {} + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + val screenRecordState: StateFlow<ScreenRecordChipModel> = // ScreenRecordRepository has the main "is the screen being recorded?" state, and // MediaProjectionRepository has information about what specifically is being recorded (a @@ -51,37 +83,55 @@ constructor( combine( screenRecordRepository.screenRecordState, mediaProjectionRepository.mediaProjectionState, - ) { screenRecordState, mediaProjectionState -> - when (screenRecordState) { - is ScreenRecordModel.DoingNothing -> { - logger.log(TAG, LogLevel.INFO, {}, { "State: DoingNothing" }) - ScreenRecordChipModel.DoingNothing - } - is ScreenRecordModel.Starting -> { - logger.log( - TAG, - LogLevel.INFO, - { long1 = screenRecordState.millisUntilStarted }, - { "State: Starting($long1)" } - ) - ScreenRecordChipModel.Starting(screenRecordState.millisUntilStarted) - } - is ScreenRecordModel.Recording -> { - val recordedTask = - if ( - mediaProjectionState is MediaProjectionState.Projecting.SingleTask - ) { - mediaProjectionState.task - } else { - null - } - logger.log( - TAG, - LogLevel.INFO, - { str1 = recordedTask?.baseIntent?.component?.packageName }, - { "State: Recording(taskPackage=$str1)" } - ) - ScreenRecordChipModel.Recording(recordedTask) + shouldAssumeIsRecording, + ) { screenRecordState, mediaProjectionState, shouldAssumeIsRecording -> + if ( + Flags.statusBarAutoStartScreenRecordChip() && + shouldAssumeIsRecording && + screenRecordState is ScreenRecordModel.Starting + ) { + logger.log( + TAG, + LogLevel.INFO, + {}, + { "State: Recording(taskPackage=null) due to force-start" }, + ) + ScreenRecordChipModel.Recording(recordedTask = null) + } else { + when (screenRecordState) { + is ScreenRecordModel.DoingNothing -> { + logger.log(TAG, LogLevel.INFO, {}, { "State: DoingNothing" }) + ScreenRecordChipModel.DoingNothing + } + + is ScreenRecordModel.Starting -> { + logger.log( + TAG, + LogLevel.INFO, + { long1 = screenRecordState.millisUntilStarted }, + { "State: Starting($long1)" }, + ) + ScreenRecordChipModel.Starting(screenRecordState.millisUntilStarted) + } + + is ScreenRecordModel.Recording -> { + val recordedTask = + if ( + mediaProjectionState + is MediaProjectionState.Projecting.SingleTask + ) { + mediaProjectionState.task + } else { + null + } + logger.log( + TAG, + LogLevel.INFO, + { str1 = recordedTask?.baseIntent?.component?.packageName }, + { "State: Recording(taskPackage=$str1)" }, + ) + ScreenRecordChipModel.Recording(recordedTask) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt index f441fd644c17..4c54fc49e536 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt @@ -112,12 +112,12 @@ constructor( } override fun initializeStatusBar() { - StatusBarSimpleFragment.assertInLegacyMode() + StatusBarRootModernization.assertInLegacyMode() doStart() } private fun doStart() { - if (StatusBarSimpleFragment.isEnabled) doComposeStart() else doLegacyStart() + if (StatusBarRootModernization.isEnabled) doComposeStart() else doLegacyStart() } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt index 214151383dc6..057213fa4b18 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt @@ -21,9 +21,9 @@ import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils /** Helper for reading and using the status bar simple fragment flag state */ -object StatusBarSimpleFragment { +object StatusBarRootModernization { /** Aconfig flag for removing the fragment */ - const val FLAG_NAME = Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT + const val FLAG_NAME = Flags.FLAG_STATUS_BAR_ROOT_MODERNIZATION /** A token used for dependency declaration */ val token: FlagToken @@ -32,7 +32,7 @@ object StatusBarSimpleFragment { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.statusBarSimpleFragment() + get() = Flags.statusBarRootModernization() /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt index 16d0cc42db7f..3c8c42f6b29d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.icon import android.app.Notification import android.content.Context -import android.graphics.drawable.Drawable import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.contentDescForNotification @@ -30,15 +29,11 @@ class IconBuilder @Inject constructor(private val context: Context) { return StatusBarIconView( context, "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}", - entry.sbn + entry.sbn, ) } fun getIconContentDescription(n: Notification): CharSequence { return contentDescForNotification(context, n) } - - fun getAppIcon(n: Notification): Drawable { - return n.loadHeaderAppIcon(context) - } } 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 db804835f260..47171948f395 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 @@ -26,6 +26,7 @@ import android.os.Bundle import android.util.Log import android.view.View import android.widget.ImageView +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.tracing.traceSection import com.android.internal.statusbar.StatusBarIcon import com.android.systemui.Flags @@ -44,7 +45,6 @@ import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext /** @@ -152,13 +152,7 @@ constructor( setIcon(entry, sensitiveIconDescriptor, shelfIcon) setIcon(entry, sensitiveIconDescriptor, aodIcon) entry.icons = - IconPack.buildPack( - sbIcon, - sbChipIcon, - shelfIcon, - aodIcon, - entry.icons, - ) + IconPack.buildPack(sbIcon, sbChipIcon, shelfIcon, aodIcon, entry.icons) } catch (e: InflationException) { entry.icons = IconPack.buildEmptyPack(entry.icons) throw e @@ -182,7 +176,7 @@ constructor( Log.wtf( TAG, "Updating using the cache is not supported when the " + - "notifications_background_icons flag is off" + "notifications_background_icons flag is off", ) } if (!usingCache || !Flags.notificationsBackgroundIcons()) { @@ -249,10 +243,6 @@ constructor( val (icon: Icon?, type: StatusBarIcon.Type) = if (showPeopleAvatar) { createPeopleAvatar(entry) to StatusBarIcon.Type.PeopleAvatar - } else if ( - android.app.Flags.notificationsUseMonochromeAppIcon() && n.shouldUseAppIcon() - ) { - n.smallIcon to StatusBarIcon.Type.MaybeMonochromeAppIcon } else { n.smallIcon to StatusBarIcon.Type.NotifSmallIcon } @@ -267,33 +257,25 @@ constructor( private fun getCachedIconDescriptor( entry: NotificationEntry, - showPeopleAvatar: Boolean + showPeopleAvatar: Boolean, ): StatusBarIcon? { val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor - val appIconDescriptor = entry.icons.appIconDescriptor val smallIconDescriptor = entry.icons.smallIconDescriptor // If cached, return corresponding cached values return when { showPeopleAvatar && peopleAvatarDescriptor != null -> peopleAvatarDescriptor - android.app.Flags.notificationsUseMonochromeAppIcon() && appIconDescriptor != null -> - appIconDescriptor smallIconDescriptor != null -> smallIconDescriptor else -> null } } private fun cacheIconDescriptor(entry: NotificationEntry, descriptor: StatusBarIcon) { - if ( - android.app.Flags.notificationsUseAppIcon() || - android.app.Flags.notificationsUseMonochromeAppIcon() - ) { - // If either of the new icon flags is enabled, we cache the icon all the time. + if (android.app.Flags.notificationsRedesignAppIcons()) { + // Although we're not actually using the app icon in the status bar, let's make sure + // we cache the icon all the time when the flag is on. when (descriptor.type) { StatusBarIcon.Type.PeopleAvatar -> entry.icons.peopleAvatarDescriptor = descriptor - // When notificationsUseMonochromeAppIcon is enabled, we use the appIconDescriptor. - StatusBarIcon.Type.MaybeMonochromeAppIcon -> - entry.icons.appIconDescriptor = descriptor // When notificationsUseAppIcon is enabled, the app icon overrides the small icon. // But either way, it's a good idea to cache the descriptor. else -> entry.icons.smallIconDescriptor = descriptor @@ -312,7 +294,7 @@ constructor( private fun setIcon( entry: NotificationEntry, iconDescriptor: StatusBarIcon, - iconView: StatusBarIconView + iconView: StatusBarIconView, ) { iconView.setShowsConversation(showsConversation(entry, iconView, iconDescriptor)) iconView.setTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP) @@ -323,7 +305,7 @@ constructor( private fun Icon.toStatusBarIcon( entry: NotificationEntry, - type: StatusBarIcon.Type + type: StatusBarIcon.Type, ): StatusBarIcon { val n = entry.sbn.notification return StatusBarIcon( @@ -333,7 +315,7 @@ constructor( n.iconLevel, n.number, iconBuilder.getIconContentDescription(n), - type + type, ) } @@ -347,7 +329,7 @@ constructor( } catch (e: Exception) { Log.e( TAG, - "Error calling LauncherApps#getShortcutIcon for notification $entry: $e" + "Error calling LauncherApps#getShortcutIcon for notification $entry: $e", ) } } @@ -431,7 +413,7 @@ constructor( private fun showsConversation( entry: NotificationEntry, iconView: StatusBarIconView, - iconDescriptor: StatusBarIcon + iconDescriptor: StatusBarIcon, ): Boolean { val usedInSensitiveContext = iconView === entry.icons.shelfIcon || iconView === entry.icons.aodIcon diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java index 611cebcf6427..cb6be661c7f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java @@ -34,7 +34,6 @@ public final class IconPack { @Nullable private final StatusBarIconView mAodIcon; @Nullable private StatusBarIcon mSmallIconDescriptor; - @Nullable private StatusBarIcon mAppIconDescriptor; @Nullable private StatusBarIcon mPeopleAvatarDescriptor; private boolean mIsImportantConversation; @@ -127,15 +126,6 @@ public final class IconPack { mPeopleAvatarDescriptor = peopleAvatarDescriptor; } - @Nullable - StatusBarIcon getAppIconDescriptor() { - return mAppIconDescriptor; - } - - void setAppIconDescriptor(@Nullable StatusBarIcon appIconDescriptor) { - mAppIconDescriptor = appIconDescriptor; - } - boolean isImportantConversation() { return mIsImportantConversation; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt index 6b5642af3f10..83f56a092bc6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt @@ -20,7 +20,6 @@ import android.graphics.Rect import android.view.View import com.android.app.tracing.traceSection import com.android.internal.util.ContrastColorUtil -import com.android.systemui.Flags import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.NO_COLOR @@ -36,11 +35,9 @@ object StatusBarIconViewBinder { suspend fun bindColor(view: StatusBarIconView, color: Flow<Int>) { color.collectTracingEach("SBIV#bindColor") { color -> - // Don't change the icon color if an app icon experiment is enabled. - if (!android.app.Flags.notificationsUseAppIcon()) { - view.staticDrawableColor = color - } - // Continue changing the overflow dot color + // Set the color for the icons + view.staticDrawableColor = color + // Set the color for the overflow dot view.setDecorColor(color) } } @@ -59,14 +56,12 @@ object StatusBarIconViewBinder { contrastColorUtil: ContrastColorUtil, ) { iconColors.collectTracingEach("SBIV#bindIconColors") { colors -> - // Don't change the icon color if an app icon experiment is enabled. - if (!android.app.Flags.notificationsUseAppIcon()) { - val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L) - val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil) - view.staticDrawableColor = - if (isColorized) colors.staticDrawableColor(view.viewBounds) else NO_COLOR - } - // Continue changing the overflow dot color + // Set the icon color + val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L) + val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil) + view.staticDrawableColor = + if (isColorized) colors.staticDrawableColor(view.viewBounds) else NO_COLOR + // Set the color for the overflow dot view.setDecorColor(colors.tint) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index f3521234e67a..b622defbef98 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -203,11 +203,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple addRemainingTransformTypes(); updateCropToPaddingForImageViews(); Notification n = row.getEntry().getSbn().getNotification(); - if (n.shouldUseAppIcon()) { - mIcon.setTag(ImageTransformState.ICON_TAG, n.getAppIcon()); - } else { - mIcon.setTag(ImageTransformState.ICON_TAG, n.getSmallIcon()); - } + mIcon.setTag(ImageTransformState.ICON_TAG, n.getSmallIcon()); // We need to reset all views that are no longer transforming in case a view was previously // transformed, but now we decided to transform its container instead. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 7389086296a3..80c8e8b2a109 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -203,7 +203,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.core.StatusBarConnectedDisplays; import com.android.systemui.statusbar.core.StatusBarInitializer; -import com.android.systemui.statusbar.core.StatusBarSimpleFragment; +import com.android.systemui.statusbar.core.StatusBarRootModernization; import com.android.systemui.statusbar.data.model.StatusBarMode; import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -1231,7 +1231,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { checkBarModes(); }); } - if (!StatusBarSimpleFragment.isEnabled() && !StatusBarConnectedDisplays.isEnabled()) { + if (!StatusBarRootModernization.isEnabled() && !StatusBarConnectedDisplays.isEnabled()) { // When the flag is on, we register the fragment as a core startable and this is not // needed mStatusBarInitializer.initializeStatusBar(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt index ba878edc1132..58386b0cad7c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt @@ -30,7 +30,7 @@ import com.android.systemui.statusbar.core.StatusBarInitializer import com.android.systemui.statusbar.core.StatusBarInitializerImpl import com.android.systemui.statusbar.core.StatusBarInitializerStore import com.android.systemui.statusbar.core.StatusBarOrchestrator -import com.android.systemui.statusbar.core.StatusBarSimpleFragment +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStoreModule import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore @@ -87,7 +87,7 @@ interface StatusBarPhoneModule { return if (StatusBarConnectedDisplays.isEnabled) { // Will be started through MultiDisplayStatusBarStarter CoreStartable.NOP - } else if (StatusBarSimpleFragment.isEnabled) { + } else if (StatusBarRootModernization.isEnabled) { defaultInitializerLazy.get() } else { // Will be started through CentralSurfaces 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 5cc44762dde8..c55a63c9edf5 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 @@ -58,7 +58,7 @@ import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; import com.android.systemui.statusbar.core.StatusBarConnectedDisplays; -import com.android.systemui.statusbar.core.StatusBarSimpleFragment; +import com.android.systemui.statusbar.core.StatusBarRootModernization; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; @@ -365,7 +365,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mPrimaryOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip_primary); mSecondaryOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip_secondary); - if (!StatusBarSimpleFragment.isEnabled()) { + if (!StatusBarRootModernization.isEnabled()) { showEndSideContent(false); showClock(false); } @@ -464,7 +464,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue super.onPause(); mCommandQueue.removeCallback(this); mStatusBarStateController.removeCallback(this); - if (!StatusBarSimpleFragment.isEnabled()) { + if (!StatusBarRootModernization.isEnabled()) { mOngoingCallController.removeCallback(mOngoingCallListener); } mAnimationScheduler.removeCallback(this); @@ -507,7 +507,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons, displayId); } - if (!StatusBarSimpleFragment.isEnabled()) { + if (!StatusBarRootModernization.isEnabled()) { updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false); } Trace.endSection(); @@ -528,7 +528,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue new StatusBarVisibilityChangeListener() { @Override public void onStatusBarVisibilityMaybeChanged() { - if (StatusBarSimpleFragment.isEnabled()) { + if (StatusBarRootModernization.isEnabled()) { return; } updateStatusBarVisibilities(/* animate= */ true); @@ -536,7 +536,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void onTransitionFromLockscreenToDreamStarted() { - if (StatusBarSimpleFragment.isEnabled()) { + if (StatusBarRootModernization.isEnabled()) { return; } mTransitionFromLockscreenToDreamStarted = true; @@ -547,7 +547,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue boolean hasPrimaryOngoingActivity, boolean hasSecondaryOngoingActivity, boolean shouldAnimate) { - if (StatusBarSimpleFragment.isEnabled()) { + if (StatusBarRootModernization.isEnabled()) { return; } mHasPrimaryOngoingActivity = hasPrimaryOngoingActivity; @@ -558,7 +558,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void onIsHomeStatusBarAllowedBySceneChanged( boolean isHomeStatusBarAllowedByScene) { - if (StatusBarSimpleFragment.isEnabled()) { + if (StatusBarRootModernization.isEnabled()) { return; } mHomeStatusBarAllowedByScene = isHomeStatusBarAllowedByScene; @@ -568,7 +568,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void disable(int displayId, int state1, int state2, boolean animate) { - if (StatusBarSimpleFragment.isEnabled()) { + if (StatusBarRootModernization.isEnabled()) { return; } if (displayId != getContext().getDisplayId()) { @@ -582,7 +582,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void updateStatusBarVisibilities(boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); StatusBarVisibilityModel previousModel = mLastModifiedVisibility; StatusBarVisibilityModel newModel = calculateInternalModel(mLastSystemVisibility); @@ -623,7 +623,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private StatusBarVisibilityModel calculateInternalModel( StatusBarVisibilityModel externalModel) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); // TODO(b/328393714) use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead. boolean headsUpVisible = @@ -677,7 +677,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * mLastModifiedVisibility. */ private void updateNotificationIconAreaAndOngoingActivityChip(boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); StatusBarVisibilityModel visibilityModel = mLastModifiedVisibility; boolean disableNotifications = !visibilityModel.getShowNotificationIcons(); @@ -714,7 +714,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private boolean shouldHideStatusBar() { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); boolean isDefaultDisplay = getContext().getDisplayId() == Display.DEFAULT_DISPLAY; boolean shouldHideForCurrentDisplay = @@ -776,7 +776,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void hideEndSideContent(boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); if (!animate || !mAnimationsEnabled) { mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER); } else { @@ -786,7 +786,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void showEndSideContent(boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); if (!animate || !mAnimationsEnabled) { mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER); return; @@ -803,18 +803,18 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void hideClock(boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); animateHiddenState(mClockView, clockHiddenMode(), animate); } private void showClock(boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); animateShow(mClockView, animate); } /** Hides the primary ongoing activity chip. */ private void hidePrimaryOngoingActivityChip(boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); animateHiddenState(mPrimaryOngoingActivityChip, View.GONE, animate); } @@ -826,18 +826,18 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * activities. See b/332662551. */ private void showPrimaryOngoingActivityChip(boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); animateShow(mPrimaryOngoingActivityChip, animate); } private void hideSecondaryOngoingActivityChip(boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); animateHiddenState(mSecondaryOngoingActivityChip, View.GONE, animate); } private void showSecondaryOngoingActivityChip(boolean animate) { StatusBarNotifChips.assertInNewMode(); - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); animateShow(mSecondaryOngoingActivityChip, animate); } @@ -846,7 +846,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * don't set the clock GONE otherwise it'll mess up the animation. */ private int clockHiddenMode() { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); if (!mShadeExpansionStateManager.isClosed() && !mKeyguardStateController.isShowing() && !mStatusBarStateController.isDozing()) { return View.INVISIBLE; @@ -855,24 +855,24 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } public void hideNotificationIconArea(boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); animateHide(mNotificationIconAreaInner, animate); } public void showNotificationIconArea(boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); animateShow(mNotificationIconAreaInner, animate); } public void hideOperatorName(boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); if (mOperatorNameViewController != null) { animateHide(mOperatorNameViewController.getView(), animate); } } public void showOperatorName(boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); if (mOperatorNameViewController != null) { animateShow(mOperatorNameViewController.getView(), animate); } @@ -882,7 +882,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * Animate a view to INVISIBLE or GONE */ private void animateHiddenState(final View v, int state, boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); v.animate().cancel(); if (!animate || !mAnimationsEnabled) { v.setAlpha(0f); @@ -902,7 +902,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * Hides a view. */ private void animateHide(final View v, boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); animateHiddenState(v, View.INVISIBLE, animate); } @@ -910,7 +910,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * Shows a view, and synchronizes the animation with Keyguard exit animations, if applicable. */ private void animateShow(View v, boolean animate) { - StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarRootModernization.assertInLegacyMode(); v.animate().cancel(); v.setVisibility(View.VISIBLE); if (!animate || !mAnimationsEnabled) { @@ -949,7 +949,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mOperatorNameViewController.init(); // This view should not be visible on lock-screen if (mKeyguardStateController.isShowing()) { - if (!StatusBarSimpleFragment.isEnabled()) { + if (!StatusBarRootModernization.isEnabled()) { hideOperatorName(false); } } @@ -957,7 +957,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void initOngoingCallChip() { - if (!StatusBarSimpleFragment.isEnabled()) { + if (!StatusBarRootModernization.isEnabled()) { mOngoingCallController.addCallback(mOngoingCallListener); } // TODO(b/364653005): Do we also need to set the secondary activity chip? @@ -969,7 +969,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void onDozingChanged(boolean isDozing) { - if (StatusBarSimpleFragment.isEnabled()) { + if (StatusBarRootModernization.isEnabled()) { return; } updateStatusBarVisibilities(/* animate= */ false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt index eaf15a8cbe17..e4929b574bd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt @@ -20,7 +20,7 @@ import android.view.View import androidx.core.animation.Interpolator import androidx.core.animation.ValueAnimator import com.android.app.animation.InterpolatorsAndroidX -import com.android.systemui.statusbar.core.StatusBarSimpleFragment +import com.android.systemui.statusbar.core.StatusBarRootModernization /** * A controller that keeps track of multiple sources applying alpha value changes to a view. It will @@ -75,7 +75,7 @@ constructor(private val view: View, private val initialAlpha: Float = 1f) { private fun applyAlphaToView() { val minAlpha = getMinAlpha() - if (!StatusBarSimpleFragment.isEnabled) { + if (!StatusBarRootModernization.isEnabled) { view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE view.alpha = minAlpha } 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 11d73397ca22..2177e025b976 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 @@ -22,6 +22,7 @@ import android.view.View import androidx.lifecycle.Lifecycle 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 @@ -30,13 +31,12 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel -import com.android.systemui.statusbar.core.StatusBarSimpleFragment +import com.android.systemui.statusbar.core.StatusBarRootModernization 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 com.android.app.tracing.coroutines.launchTraced as launch /** * Interface to assist with binding the [CollapsedStatusBarFragment] to [HomeStatusBarViewModel]. @@ -91,7 +91,7 @@ class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinde launch { viewModel.primaryOngoingActivityChip.collect { primaryChipModel -> OngoingActivityChipBinder.bind(primaryChipModel, primaryChipView) - if (StatusBarSimpleFragment.isEnabled) { + if (StatusBarRootModernization.isEnabled) { when (primaryChipModel) { is OngoingActivityChipModel.Shown -> primaryChipView.show(shouldAnimateChange = true) @@ -133,7 +133,7 @@ class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinde // enough space for it. OngoingActivityChipBinder.bind(chips.secondary, secondaryChipView) - if (StatusBarSimpleFragment.isEnabled) { + if (StatusBarRootModernization.isEnabled) { primaryChipView.adjustVisibility(chips.primary.toVisibilityModel()) secondaryChipView.adjustVisibility( chips.secondary.toVisibilityModel() @@ -160,7 +160,7 @@ class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinde } } - if (StatusBarSimpleFragment.isEnabled) { + if (StatusBarRootModernization.isEnabled) { val clockView = view.requireViewById<View>(R.id.clock) launch { viewModel.isClockVisible.collect { clockView.adjustVisibility(it) } } 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 247abc3da175..a9c2f72f7b18 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 @@ -104,7 +104,7 @@ fun StatusBarRoot( darkIconDispatcher: DarkIconDispatcher, onViewCreated: (ViewGroup) -> Unit, ) { - // None of these methods are used when [StatusBarSimpleFragment] is on. + // None of these methods are used when [StatusBarRootModernization] is on. // This can be deleted once the fragment is gone val nopVisibilityChangeListener = object : StatusBarVisibilityChangeListener { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java index 3f6ef16e2e5e..b79d39e65682 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java @@ -35,7 +35,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.compose.ComposeInitializer; -import com.android.systemui.statusbar.core.StatusBarSimpleFragment; +import com.android.systemui.statusbar.core.StatusBarRootModernization; import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController; /** @@ -64,7 +64,7 @@ public class StatusBarWindowView extends FrameLayout { public void onAttachedToWindow() { super.onAttachedToWindow(); - if (StatusBarSimpleFragment.isEnabled()) { + if (StatusBarRootModernization.isEnabled()) { ComposeInitializer.INSTANCE.onAttachedToWindow(this); } } @@ -73,7 +73,7 @@ public class StatusBarWindowView extends FrameLayout { public void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (StatusBarSimpleFragment.isEnabled()) { + if (StatusBarRootModernization.isEnabled()) { ComposeInitializer.INSTANCE.onDetachedFromWindow(this); } } diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 493aa8c11b18..e9a33e062c60 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -19,12 +19,18 @@ package com.android.systemui.user.data.repository import android.annotation.SuppressLint import android.annotation.UserIdInt +import android.app.admin.DevicePolicyManager import android.content.Context +import android.content.IntentFilter import android.content.pm.UserInfo +import android.content.res.Resources import android.os.UserHandle import android.os.UserManager import android.provider.Settings import androidx.annotation.VisibleForTesting +import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.internal.statusbar.IStatusBarService +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -38,6 +44,7 @@ import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -49,11 +56,12 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext @@ -100,6 +108,12 @@ interface UserRepository { /** Whether refresh users should be paused. */ var isRefreshUsersPaused: Boolean + /** Whether logout for secondary users is enabled by admin device policy. */ + val isSecondaryUserLogoutEnabled: StateFlow<Boolean> + + /** Whether logout into system user is enabled. */ + val isLogoutToSystemUserEnabled: StateFlow<Boolean> + /** Asynchronously refresh the list of users. This will cause [userInfos] to be updated. */ fun refreshUsers() @@ -109,6 +123,12 @@ interface UserRepository { fun isUserSwitcherEnabled(): Boolean + /** Performs logout logout for secondary users. */ + suspend fun logOutSecondaryUser() + + /** Performs logout into the system user. */ + suspend fun logOutToSystemUser() + /** * Returns the user ID of the "main user" of the device. This user may have access to certain * features which are limited to at most one user. There will never be more than one main user @@ -131,12 +151,16 @@ class UserRepositoryImpl @Inject constructor( @Application private val appContext: Context, + @Main private val resources: Resources, private val manager: UserManager, @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, @Background private val backgroundDispatcher: CoroutineDispatcher, private val globalSettings: GlobalSettings, private val tracker: UserTracker, + private val devicePolicyManager: DevicePolicyManager, + private val broadcastDispatcher: BroadcastDispatcher, + private val statusBarService: IStatusBarService, ) : UserRepository { private val _userSwitcherSettings: StateFlow<UserSwitcherSettingsModel> = @@ -147,7 +171,7 @@ constructor( SETTING_SIMPLE_USER_SWITCHER, Settings.Global.ADD_USERS_WHEN_LOCKED, Settings.Global.USER_SWITCHER_ENABLED, - ), + ) ) .onStart { emit(Unit) } // Forces an initial update. .map { getSettings() } @@ -163,6 +187,7 @@ constructor( override var mainUserId: Int = UserHandle.USER_NULL private set + override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL private set @@ -221,12 +246,73 @@ constructor( .stateIn( applicationScope, SharingStarted.Eagerly, - initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus) + initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus), ) } override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo } + /** Whether the secondary user logout is enabled by the admin device policy. */ + private val isSecondaryUserLogoutSupported: Flow<Boolean> = + broadcastDispatcher + .broadcastFlow( + filter = + IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED) + ) { intent, _ -> + if ( + DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED == intent.action + ) { + Unit + } else { + null + } + } + .filterNotNull() + .onStart { emit(Unit) } + .map { _ -> devicePolicyManager.isLogoutEnabled() } + .flowOn(backgroundDispatcher) + + @SuppressLint("MissingPermission") + override val isSecondaryUserLogoutEnabled: StateFlow<Boolean> = + selectedUser + .flatMapLatestConflated { selectedUser -> + if (selectedUser.isEligibleForLogout()) { + isSecondaryUserLogoutSupported + } else { + flowOf(false) + } + } + .stateIn(applicationScope, SharingStarted.Eagerly, false) + + @SuppressLint("MissingPermission") + override val isLogoutToSystemUserEnabled: StateFlow<Boolean> = + selectedUser + .flatMapLatestConflated { selectedUser -> + if (selectedUser.isEligibleForLogout()) { + flowOf( + resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen) + ) + } else { + flowOf(false) + } + } + .stateIn(applicationScope, SharingStarted.Eagerly, false) + + @SuppressLint("MissingPermission") + override suspend fun logOutSecondaryUser() { + if (isSecondaryUserLogoutEnabled.value) { + withContext(backgroundDispatcher) { devicePolicyManager.logoutUser() } + } + } + + override suspend fun logOutToSystemUser() { + // TODO(b/377493351) : start using proper logout API once it is available. + // Using reboot is a temporary solution. + if (isLogoutToSystemUserEnabled.value) { + withContext(backgroundDispatcher) { statusBarService.reboot(false) } + } + } + @SuppressLint("MissingPermission") override fun refreshUsers() { applicationScope.launch { @@ -277,10 +363,7 @@ constructor( ) != 0 val isAddUsersFromLockscreen = - globalSettings.getInt( - Settings.Global.ADD_USERS_WHEN_LOCKED, - 0, - ) != 0 + globalSettings.getInt(Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0 val isUserSwitcherEnabled = globalSettings.getInt( @@ -309,3 +392,11 @@ constructor( @VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher" } } + +fun SelectedUserModel.isEligibleForLogout(): Boolean { + // TODO(b/206032495): should call mDevicePolicyManager.getLogoutUserId() instead of + // hardcode it to USER_SYSTEM so it properly supports headless system user mode + // (and then call mDevicePolicyManager.clearLogoutUser() after switched) + return selectionStatus == SelectionStatus.SELECTION_COMPLETE && + userInfo.id != android.os.UserHandle.USER_SYSTEM +} diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt new file mode 100644 index 000000000000..f2dd25fecf08 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.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.systemui.user.domain.interactor + +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.user.data.repository.UserRepository +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn + +/** Encapsulates business logic to for the logout. */ +@SysUISingleton +class UserLogoutInteractor +@Inject +constructor( + private val userRepository: UserRepository, + @Application private val applicationScope: CoroutineScope, +) { + + val isLogoutEnabled: StateFlow<Boolean> = + combine( + userRepository.isSecondaryUserLogoutEnabled, + userRepository.isLogoutToSystemUserEnabled, + Boolean::or, + ) + .stateIn(applicationScope, SharingStarted.Eagerly, false) + + fun logOut() { + applicationScope.launch { + if (userRepository.isSecondaryUserLogoutEnabled.value) { + userRepository.logOutSecondaryUser() + } else if (userRepository.isLogoutToSystemUserEnabled.value) { + userRepository.logOutToSystemUser() + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt index 71335ec84c86..bc3726d362e2 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.CoroutineDispatcher * Repository for observing values of [Settings.Secure] for the currently active user. That means * when user is switched and the new user has different value, flow will emit new value. */ +// TODO: b/377244768 - Make internal once call sites inject SecureSettingsRepository instead. @SysUISingleton class UserAwareSecureSettingsRepository @Inject diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt index a31b8d943a1b..49a0f14d6b3b 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt @@ -16,7 +16,6 @@ package com.android.systemui.util.settings.repository -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.settings.SettingsProxyExt.observerFlow @@ -34,10 +33,10 @@ import kotlinx.coroutines.withContext /** * Repository for observing values of a [UserSettingsProxy], for the currently active user. That - * means that when user is switched and the new user has a different value, the flow will emit the - * new value. + * means that when the user is switched and the new user has a different value, the flow will emit + * the new value. */ -@SysUISingleton +// TODO: b/377244768 - Make internal when UserAwareSecureSettingsRepository can be made internal. @OptIn(ExperimentalCoroutinesApi::class) abstract class UserAwareSettingsRepository( private val userSettings: UserSettingsProxy, diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt index 8b1fca551d51..4b01ded16495 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt @@ -17,12 +17,10 @@ package com.android.systemui.util.settings.repository import android.provider.Settings -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.settings.SystemSettings -import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineDispatcher @@ -30,10 +28,8 @@ import kotlinx.coroutines.CoroutineDispatcher * Repository for observing values of [Settings.Secure] for the currently active user. That means * when user is switched and the new user has different value, flow will emit new value. */ -@SysUISingleton -class UserAwareSystemSettingsRepository -@Inject -constructor( +// TODO: b/377244768 - Make internal once call sites inject SystemSettingsRepository instead. +class UserAwareSystemSettingsRepository( systemSettings: SystemSettings, userRepository: UserRepository, @Background backgroundDispatcher: CoroutineDispatcher, diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt index 9440a9364b62..fb157958a630 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt @@ -16,6 +16,7 @@ package com.android.systemui.volume.dialog.dagger +import com.android.systemui.volume.dialog.dagger.module.VolumeDialogModule import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent @@ -28,7 +29,7 @@ import kotlinx.coroutines.CoroutineScope * [com.android.systemui.volume.dialog.VolumeDialogPlugin] and lives alongside it. */ @VolumeDialogScope -@Subcomponent(modules = []) +@Subcomponent(modules = [VolumeDialogModule::class]) interface VolumeDialogComponent { /** diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt new file mode 100644 index 000000000000..025e269b70b6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.dagger.module + +import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepository +import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepositoryImpl +import dagger.Binds +import dagger.Module + +/** Dagger module for volume dialog code in the volume package */ +@Module +interface VolumeDialogModule { + + @Binds + fun bindVolumeDialogRingerFeedbackRepository( + ringerFeedbackRepository: VolumeDialogRingerFeedbackRepositoryImpl + ): VolumeDialogRingerFeedbackRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepository.kt new file mode 100644 index 000000000000..263972b937d4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepository.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.systemui.volume.dialog.ringer.data.repository + +import android.content.Context +import com.android.systemui.Prefs +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +interface VolumeDialogRingerFeedbackRepository { + + /** gets number of shown toasts */ + suspend fun getToastCount(): Int + + /** updates number of shown toasts */ + suspend fun updateToastCount(toastCount: Int) +} + +class VolumeDialogRingerFeedbackRepositoryImpl +@Inject +constructor( + @Application private val applicationContext: Context, + @Background val backgroundDispatcher: CoroutineDispatcher, +) : VolumeDialogRingerFeedbackRepository { + + override suspend fun getToastCount(): Int = + withContext(backgroundDispatcher) { + return@withContext Prefs.getInt( + applicationContext, + Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, + 0, + ) + } + + override suspend fun updateToastCount(toastCount: Int) { + withContext(backgroundDispatcher) { + Prefs.putInt(applicationContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, toastCount + 1) + } + } +} 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 281e57fec855..b83613ba4f8c 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 @@ -26,6 +26,7 @@ import com.android.settingslib.volume.shared.model.RingerMode import com.android.systemui.plugins.VolumeDialogController import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor +import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepository import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel import javax.inject.Inject @@ -45,6 +46,7 @@ constructor( volumeDialogStateInteractor: VolumeDialogStateInteractor, private val controller: VolumeDialogController, private val audioSystemRepository: AudioSystemRepository, + private val ringerFeedbackRepository: VolumeDialogRingerFeedbackRepository, ) { val ringerModel: Flow<VolumeDialogRingerModel> = @@ -84,4 +86,12 @@ constructor( fun scheduleTouchFeedback() { controller.scheduleTouchFeedback() } + + suspend fun getToastCount(): Int { + return ringerFeedbackRepository.getToastCount() + } + + suspend fun updateToastCount(toastCount: Int) { + ringerFeedbackRepository.updateToastCount(toastCount) + } } 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 d4da226152f3..e040638324ac 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 @@ -16,17 +16,23 @@ package com.android.systemui.volume.dialog.ringer.ui.viewmodel +import android.content.Context import android.media.AudioAttributes import android.media.AudioManager.RINGER_MODE_NORMAL import android.media.AudioManager.RINGER_MODE_SILENT import android.media.AudioManager.RINGER_MODE_VIBRATE import android.os.VibrationEffect +import android.widget.Toast +import com.android.internal.R as internalR +import com.android.settingslib.Utils import com.android.settingslib.volume.shared.model.RingerMode +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.volume.Events import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog +import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.ringer.domain.VolumeDialogRingerInteractor import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel import com.android.systemui.volume.dialog.shared.VolumeDialogLogger @@ -40,26 +46,37 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +private const val SHOW_RINGER_TOAST_COUNT = 12 class VolumeDialogRingerDrawerViewModel @AssistedInject constructor( + @Application private val applicationContext: Context, @VolumeDialog private val coroutineScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, private val interactor: VolumeDialogRingerInteractor, private val vibrator: VibratorHelper, private val volumeDialogLogger: VolumeDialogLogger, + private val visibilityInteractor: VolumeDialogVisibilityInteractor, ) { private val drawerState = MutableStateFlow<RingerDrawerState>(RingerDrawerState.Initial) val ringerViewModel: StateFlow<RingerViewModelState> = combine(interactor.ringerModel, drawerState) { ringerModel, state -> + level = ringerModel.level + levelMax = ringerModel.levelMax ringerModel.toViewModel(state) } .flowOn(backgroundDispatcher) .stateIn(coroutineScope, SharingStarted.Eagerly, RingerViewModelState.Unavailable) + // Level and Maximum level of Ring Stream. + private var level = -1 + private var levelMax = -1 + // Vibration attributes. private val sonificiationVibrationAttributes = AudioAttributes.Builder() @@ -71,8 +88,10 @@ constructor( if (drawerState.value is RingerDrawerState.Open) { Events.writeEvent(Events.EVENT_RINGER_TOGGLE, ringerMode.value) provideTouchFeedback(ringerMode) + maybeShowToast(ringerMode) interactor.setRingerMode(ringerMode) } + visibilityInteractor.resetDismissTimeout() drawerState.value = when (drawerState.value) { is RingerDrawerState.Initial -> { @@ -201,6 +220,46 @@ constructor( } } + private fun maybeShowToast(ringerMode: RingerMode) { + coroutineScope.launch { + val seenToastCount = interactor.getToastCount() + if (seenToastCount > SHOW_RINGER_TOAST_COUNT) { + return@launch + } + + val toastText = + when (ringerMode.value) { + RINGER_MODE_NORMAL -> { + if (level != -1 && levelMax != -1) { + applicationContext.getString( + R.string.volume_dialog_ringer_guidance_ring, + Utils.formatPercentage(level.toLong(), levelMax.toLong()), + ) + } else { + null + } + } + + RINGER_MODE_SILENT -> + applicationContext.getString( + internalR.string.volume_dialog_ringer_guidance_silent + ) + + RINGER_MODE_VIBRATE -> + applicationContext.getString( + internalR.string.volume_dialog_ringer_guidance_vibrate + ) + + else -> + applicationContext.getString( + internalR.string.volume_dialog_ringer_guidance_vibrate + ) + } + toastText?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_SHORT).show() } + interactor.updateToastCount(seenToastCount) + } + } + @AssistedFactory interface Factory { fun create(): VolumeDialogRingerDrawerViewModel diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 8039e00159f0..073781e6101d 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -271,6 +271,12 @@ public final class WMShell implements // No op. } }, mSysUiMainExecutor); + pip.addOnIsInPipStateChangedListener((isInPip) -> { + if (!isInPip) { + mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, false) + .commitUpdate(mDisplayTracker.getDefaultDisplayId()); + } + }); mSysUiState.addCallback(sysUiStateFlag -> { mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0; pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 3d9eb53d436a..a39ca5de787d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -103,6 +103,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.FlagsParameterization; import android.service.dreams.IDreamManager; import android.service.trust.TrustAgentService; @@ -129,6 +130,7 @@ import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated; import com.android.keyguard.logging.KeyguardUpdateMonitorLogger; import com.android.keyguard.logging.SimLogger; import com.android.settingslib.fuelgauge.BatteryStatus; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; @@ -190,6 +192,7 @@ import platform.test.runner.parameterized.Parameters; @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper +@EnableFlags(Flags.FLAG_USER_ENCRYPTED_SOURCE) public class KeyguardUpdateMonitorTest extends SysuiTestCase { private static final String PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY = "test_app_fp_listen_on_occluding_activity"; @@ -1292,12 +1295,15 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testIsUserUnlocked() { + when(mUserManager.isUserUnlocked(mSelectedUserInteractor.getSelectedUserId())).thenReturn( + true); // mUserManager will report the user as unlocked on @Before assertThat( mKeyguardUpdateMonitor.isUserUnlocked(mSelectedUserInteractor.getSelectedUserId())) .isTrue(); // Invalid user should not be unlocked. int randomUser = 99; + when(mUserManager.isUserUnlocked(randomUser)).thenReturn(false); assertThat(mKeyguardUpdateMonitor.isUserUnlocked(randomUser)).isFalse(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index df50f765349c..24bca70fd41f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -31,7 +31,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.IActivityManager; -import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.content.pm.PackageManager; import android.content.pm.UserInfo; @@ -80,6 +79,7 @@ import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.statusbar.window.StatusBarWindowControllerStore; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.android.systemui.user.domain.interactor.UserLogoutInteractor; import com.android.systemui.util.RingerModeLiveData; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.settings.FakeGlobalSettings; @@ -106,7 +106,6 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs; @Mock private AudioManager mAudioManager; - @Mock private DevicePolicyManager mDevicePolicyManager; @Mock private LockPatternUtils mLockPatternUtils; @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private TelephonyListenerManager mTelephonyListenerManager; @@ -140,6 +139,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private DialogTransitionAnimator mDialogTransitionAnimator; @Mock private SelectedUserInteractor mSelectedUserInteractor; + @Mock private UserLogoutInteractor mLogoutInteractor; @Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher; @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback; @@ -166,7 +166,6 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext, mWindowManagerFuncs, mAudioManager, - mDevicePolicyManager, mLockPatternUtils, mBroadcastDispatcher, mTelephonyListenerManager, @@ -198,6 +197,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mKeyguardUpdateMonitor, mDialogTransitionAnimator, mSelectedUserInteractor, + mLogoutInteractor, mInteractor); mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt index 3bfde68def50..909680866d20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -59,6 +59,7 @@ import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock +import com.android.wm.shell.back.BackAnimation import com.android.wm.shell.sysui.ShellInterface import com.google.common.util.concurrent.MoreExecutors import java.util.Optional @@ -120,6 +121,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { private lateinit var unfoldTransitionProgressForwarder: Optional<UnfoldTransitionProgressForwarder> @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock private lateinit var backAnimation: Optional<BackAnimation> @Before fun setUp() { @@ -289,6 +291,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { dumpManager, unfoldTransitionProgressForwarder, broadcastDispatcher, + backAnimation, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt index ae976a0ea703..9fb752a11f56 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.settings import android.hardware.display.DisplayManager -import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS +import android.hardware.display.DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS import android.hardware.display.DisplayManagerGlobal import android.os.Handler import android.testing.AndroidTestingRunner @@ -59,14 +59,14 @@ class DisplayTrackerImplTest : SysuiTestCase() { DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY, DisplayInfo(), - DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS, ) mSecondaryDisplay = Display( DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY + 1, DisplayInfo(), - DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS, ) `when`(displayManager.displays).thenReturn(arrayOf(mDefaultDisplay, mSecondaryDisplay)) @@ -94,7 +94,12 @@ class DisplayTrackerImplTest : SysuiTestCase() { fun registerBrightnessCallback_registersDisplayListener() { tracker.addBrightnessChangeCallback(TestCallback(), executor) verify(displayManager) - .registerDisplayListener(any(), any(), eq(EVENT_FLAG_DISPLAY_BRIGHTNESS)) + .registerDisplayListener( + any(), + any(), + eq(0L), + eq(PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS), + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 4b648a3e76e7..d1e4f646a382 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.phone.fragment; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS; -import static com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN; @@ -61,6 +60,7 @@ import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; +import com.android.systemui.statusbar.core.StatusBarRootModernization; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder; @@ -156,7 +156,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void testDisableNone() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -167,7 +167,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void testDisableSystemInfo_systemAnimationIdle_doesHide() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -185,7 +185,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() { // GIVEN the status bar hides the system info via disable flags, while there is no event CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -215,7 +215,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() { // GIVEN the status bar hides the system info via disable flags, while there is no event CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -232,7 +232,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() { // GIVEN the status bar is not disabled CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -248,7 +248,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() { // GIVEN the status bar is not disabled CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -272,7 +272,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void testDisableNotifications() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -290,7 +290,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @EnableFlags(StatusBarRootModernization.FLAG_NAME) public void testDisableNotifications_doesNothingWhenFlagEnabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -308,7 +308,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void testDisableClock() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -326,7 +326,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @EnableFlags(StatusBarRootModernization.FLAG_NAME) public void testDisableClock_doesNothingWhenFlagEnabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -345,7 +345,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void disable_shadeOpenAndShouldHide_everythingHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -363,7 +363,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void disable_shadeOpenButNotShouldHide_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -382,7 +382,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { /** Regression test for b/279790651. */ @Test @DisableSceneContainer - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -410,7 +410,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void disable_notTransitioningToOccluded_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -426,7 +426,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void disable_isTransitioningToOccluded_everythingHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -442,7 +442,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -473,7 +473,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) public void disable_noOngoingCall_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -485,7 +485,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -498,7 +498,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -511,7 +511,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) public void disable_hasOngoingCallButAlsoHun_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -524,7 +524,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) public void disable_ongoingCallEnded_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -548,7 +548,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating @@ -565,7 +565,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME}) public void screenSharingChipsDisabled_ignoresNewCallback() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -599,7 +599,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void noOngoingActivity_chipHidden() { resumeAndGetFragment(); @@ -617,7 +617,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void hasPrimaryOngoingActivity_primaryChipDisplayedAndNotificationIconsHidden() { resumeAndGetFragment(); @@ -634,8 +634,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @EnableFlags({ FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME, - FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) - public void hasPrimaryOngoingActivity_viewsUnchangedWhenSimpleFragmentFlagOn() { + StatusBarRootModernization.FLAG_NAME}) + public void hasPrimaryOngoingActivity_viewsUnchangedWhenRootModernizationFlagOn() { resumeAndGetFragment(); assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility()); @@ -660,7 +660,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME}) public void hasSecondaryOngoingActivity_butNotifsFlagOff_secondaryChipHidden() { resumeAndGetFragment(); @@ -674,7 +674,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() { resumeAndGetFragment(); @@ -689,7 +689,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME}) public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -706,7 +706,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -724,7 +724,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME}) public void hasOngoingActivityButAlsoHun_chipHidden_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -741,7 +741,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -759,7 +759,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME}) public void primaryOngoingActivityEnded_chipHidden_notifsFlagOff() { resumeAndGetFragment(); @@ -782,7 +782,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void primaryOngoingActivityEnded_chipHidden_notifsFlagOn() { resumeAndGetFragment(); @@ -805,7 +805,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void secondaryOngoingActivityEnded_chipHidden() { resumeAndGetFragment(); @@ -828,7 +828,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME}) public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating @@ -847,7 +847,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating @@ -866,7 +866,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME}) public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -899,7 +899,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -933,7 +933,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableSceneContainer - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void isHomeStatusBarAllowedByScene_false_everythingHidden() { resumeAndGetFragment(); @@ -947,7 +947,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableSceneContainer - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void isHomeStatusBarAllowedByScene_true_everythingShown() { resumeAndGetFragment(); @@ -961,7 +961,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableSceneContainer - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -979,7 +979,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableSceneContainer - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -997,7 +997,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() { resumeAndGetFragment(); @@ -1011,7 +1011,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void disable_isDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(true); @@ -1023,7 +1023,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void disable_NotDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(false); @@ -1035,7 +1035,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void disable_headsUpShouldBeVisibleTrue_clockDisabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); @@ -1046,7 +1046,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false); @@ -1100,7 +1100,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void testStatusBarIcons_hiddenThroughoutCameraLaunch() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -1123,7 +1123,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -1159,7 +1159,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + @DisableFlags(StatusBarRootModernization.FLAG_NAME) public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt index c435d3d99680..37671e0bc175 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt @@ -21,9 +21,9 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule +import com.android.systemui.statusbar.core.StatusBarRootModernization import junit.framework.Assert.assertEquals import org.junit.Before import org.junit.Rule @@ -38,7 +38,7 @@ private const val INITIAL_ALPHA = 1f @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest -@DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) +@DisableFlags(StatusBarRootModernization.FLAG_NAME) class MultiSourceMinAlphaControllerTest : SysuiTestCase() { private val view = View(context) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt index ad5242e2e036..4546b995316a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt @@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.map class FakeScreenBrightnessRepository( initialBrightnessInfo: BrightnessInfo = - BrightnessInfo(0f, 0f, 1f, HIGH_BRIGHTNESS_MODE_OFF, 1f, BRIGHTNESS_MAX_REASON_NONE) + BrightnessInfo(0f, 0f, 1f, HIGH_BRIGHTNESS_MODE_OFF, 1f, BRIGHTNESS_MAX_REASON_NONE), ) : ScreenBrightnessRepository { private val brightnessInfo = MutableStateFlow(initialBrightnessInfo) @@ -36,6 +36,8 @@ class FakeScreenBrightnessRepository( override val linearBrightness = brightnessInfo.map { LinearBrightness(it.brightness) } override val minLinearBrightness = brightnessInfo.map { LinearBrightness(it.brightnessMinimum) } override val maxLinearBrightness = brightnessInfo.map { LinearBrightness(it.brightnessMaximum) } + override val isBrightnessOverriddenByWindow = + MutableStateFlow(initialBrightnessInfo.isBrightnessOverrideByWindow).asStateFlow() override suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> { return minMaxLinearBrightness() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt index 52cdbed6e25d..2198e04eaf8a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.brightness.domain.interactor.screenBrightnessInterac import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory import com.android.systemui.kosmos.Kosmos import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor +import com.android.systemui.kosmos.brightnessWarningToast val Kosmos.brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory by Kosmos.Fixture { @@ -32,6 +33,7 @@ val Kosmos.brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory b hapticsViewModelFactory = sliderHapticsViewModelFactory, brightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor, supportsMirroring = allowsMirroring, + brightnessWarningToast = brightnessWarningToast, ) } } 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 c41493eaa9c7..8022e6e86a49 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 @@ -31,8 +31,11 @@ import com.android.systemui.keyboard.shortcut.data.source.InputShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource +import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor +import com.android.systemui.keyboard.shortcut.ui.ShortcutCustomizationDialogStarter +import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel import com.android.systemui.keyguard.data.repository.fakeCommandQueue import com.android.systemui.kosmos.Kosmos @@ -42,6 +45,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.model.sysUiState import com.android.systemui.settings.displayTracker import com.android.systemui.settings.userTracker +import com.android.systemui.statusbar.phone.systemUIDialogFactory var Kosmos.shortcutHelperAppCategoriesShortcutsSource: KeyboardShortcutGroupsSource by Kosmos.Fixture { AppCategoriesShortcutsSource(windowManager, testDispatcher) } @@ -121,3 +125,26 @@ val Kosmos.shortcutHelperViewModel by shortcutHelperCategoriesInteractor, ) } + +val Kosmos.shortcutCustomizationDialogStarterFactory by + Kosmos.Fixture { + object : ShortcutCustomizationDialogStarter.Factory { + override fun create(): ShortcutCustomizationDialogStarter { + return ShortcutCustomizationDialogStarter( + shortcutCustomizationViewModelFactory, + systemUIDialogFactory, + ) + } + } + } + +val Kosmos.shortcutCustomizationInteractor by Kosmos.Fixture { ShortcutCustomizationInteractor() } + +val Kosmos.shortcutCustomizationViewModelFactory by + Kosmos.Fixture { + object : ShortcutCustomizationViewModel.Factory { + override fun create(): ShortcutCustomizationViewModel { + return ShortcutCustomizationViewModel(shortcutCustomizationInteractor) + } + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt index 72cb1dfe38db..f43841b31c2e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt @@ -3,6 +3,9 @@ package com.android.systemui.kosmos import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.settings.brightness.ui.BrightnessWarningToast + +import com.android.systemui.util.mockito.mock import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.test.StandardTestDispatcher @@ -38,6 +41,9 @@ var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture { testScope.backgroundScope.coroutineContext } var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext } +var Kosmos.brightnessWarningToast: BrightnessWarningToast by Kosmos.Fixture { + mock<BrightnessWarningToast>() +} /** * Run this test body with a [Kosmos] as receiver, and using the [testScope] currently installed in diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt index d1b613fe7f6e..f63698a3f2f9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt @@ -21,6 +21,7 @@ import android.content.res.mainResources import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.runCurrent import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment import com.android.systemui.shade.data.repository.shadeRepository @@ -56,4 +57,5 @@ fun Kosmos.setConfigurationForMediaInRow(mediaInRow: Boolean) { } mainResources.configuration.updateFrom(config) fakeConfigurationRepository.onConfigurationChange(config) + runCurrent() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt index 88063c9b5db9..aac122c6610c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.classifier.falsingManager import com.android.systemui.haptics.msdl.msdlPlayer import com.android.systemui.haptics.vibratorHelper import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.brightnessWarningToast import com.android.systemui.plugins.activityStarter import com.android.systemui.settings.brightness.BrightnessSliderController import com.android.systemui.util.time.systemClock @@ -35,5 +36,6 @@ var Kosmos.brightnessSliderControllerFactory by msdlPlayer, systemClock, activityStarter, + brightnessWarningToast, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index ed335f9a1834..85d582a27faf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -29,6 +29,7 @@ import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.yield @@ -67,6 +68,14 @@ class FakeUserRepository @Inject constructor() : UserRepository { ) override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo } + private val _isSecondaryUserLogoutEnabled = MutableStateFlow<Boolean>(false) + override val isSecondaryUserLogoutEnabled: StateFlow<Boolean> = + _isSecondaryUserLogoutEnabled.asStateFlow() + + private val _isLogoutToSystemUserEnabled = MutableStateFlow<Boolean>(false) + override val isLogoutToSystemUserEnabled: StateFlow<Boolean> = + _isLogoutToSystemUserEnabled.asStateFlow() + override var mainUserId: Int = MAIN_USER_ID override var lastSelectedNonGuestUserId: Int = mainUserId @@ -107,6 +116,28 @@ class FakeUserRepository @Inject constructor() : UserRepository { return _userSwitcherSettings.value.isUserSwitcherEnabled } + fun setSecondaryUserLogoutEnabled(logoutEnabled: Boolean) { + _isSecondaryUserLogoutEnabled.value = logoutEnabled + } + + var logOutSecondaryUserCallCount: Int = 0 + private set + + override suspend fun logOutSecondaryUser() { + logOutSecondaryUserCallCount++ + } + + fun setLogoutToSystemUserEnabled(logoutEnabled: Boolean) { + _isLogoutToSystemUserEnabled.value = logoutEnabled + } + + var logOutToSystemUserCallCount: Int = 0 + private set + + override suspend fun logOutToSystemUser() { + logOutToSystemUserCallCount++ + } + fun setUserInfos(infos: List<UserInfo>) { _userInfos.value = infos } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorKosmos.kt new file mode 100644 index 000000000000..d06e74468d3e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorKosmos.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.user.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.user.data.repository.userRepository + +val Kosmos.userLogoutInteractor by + Kosmos.Fixture { + UserLogoutInteractor( + userRepository = userRepository, + applicationScope = applicationCoroutineScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/FakeVolumeDialogRingerFeedbackRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/FakeVolumeDialogRingerFeedbackRepository.kt new file mode 100644 index 000000000000..d42de1e6d0df --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/FakeVolumeDialogRingerFeedbackRepository.kt @@ -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 com.android.systemui.volume.dialog.ringer.data.repository + +class FakeVolumeDialogRingerFeedbackRepository : VolumeDialogRingerFeedbackRepository { + + private var seenToastCount = 0 + + override suspend fun getToastCount(): Int { + return seenToastCount + } + + override suspend fun updateToastCount(toastCount: Int) { + seenToastCount = toastCount + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt new file mode 100644 index 000000000000..44371b4615df --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.ringer.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeVolumeDialogRingerFeedbackRepository by + Kosmos.Fixture { FakeVolumeDialogRingerFeedbackRepository() } +val Kosmos.volumeDialogRingerFeedbackRepository by + Kosmos.Fixture { fakeVolumeDialogRingerFeedbackRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt index 1addd91d2ec2..a494d04ec741 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.plugins.volumeDialogController import com.android.systemui.volume.data.repository.audioSystemRepository import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor +import com.android.systemui.volume.dialog.ringer.data.repository.fakeVolumeDialogRingerFeedbackRepository val Kosmos.volumeDialogRingerInteractor by Kosmos.Fixture { @@ -29,5 +30,6 @@ val Kosmos.volumeDialogRingerInteractor by volumeDialogStateInteractor = volumeDialogStateInteractor, controller = volumeDialogController, audioSystemRepository = audioSystemRepository, + ringerFeedbackRepository = fakeVolumeDialogRingerFeedbackRepository, ) } 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 db1c01a8698c..c8ba551c518a 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 @@ -16,20 +16,24 @@ package com.android.systemui.volume.dialog.ringer.ui.viewmodel +import android.content.applicationContext import com.android.systemui.haptics.vibratorHelper import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.ringer.domain.volumeDialogRingerInteractor import com.android.systemui.volume.dialog.shared.volumeDialogLogger val Kosmos.volumeDialogRingerDrawerViewModel by Kosmos.Fixture { VolumeDialogRingerDrawerViewModel( + applicationContext = applicationContext, backgroundDispatcher = testDispatcher, coroutineScope = applicationCoroutineScope, interactor = volumeDialogRingerInteractor, vibrator = vibratorHelper, volumeDialogLogger = volumeDialogLogger, + visibilityInteractor = volumeDialogVisibilityInteractor, ) } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index 57b58d8741da..3a0f8c0b02fb 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -306,7 +306,7 @@ constructor( } private fun isOnLargeScreen(): Boolean { - return context.resources.configuration.smallestScreenWidthDp > + return context.applicationContext.resources.configuration.smallestScreenWidthDp > INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP } diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 4731cfbfc935..0c2ce8dcb698 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -376,6 +376,7 @@ filegroup { ":ravenwood-empty-res", ":framework-platform-compat-config", ":services-platform-compat-config", + "texts/ravenwood-build.prop", ], device_first_srcs: [ ":apex_icu.dat", diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java index e61a054c4c39..678a97be60a2 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -22,7 +22,6 @@ import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_R import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP; -import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -95,8 +94,6 @@ public class RavenwoodRuntimeEnvironmentController { private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer"; private static final String RAVENWOOD_NATIVE_SYSPROP_NAME = "ravenwood_sysprop"; private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime"; - private static final String RAVENWOOD_BUILD_PROP = - getRavenwoodRuntimePath() + "ravenwood-data/build.prop"; /** * When enabled, attempt to dump all thread stacks just before we hit the @@ -209,7 +206,7 @@ public class RavenwoodRuntimeEnvironmentController { System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME)); // Do the basic set up for the android sysprops. - RavenwoodSystemProperties.initialize(RAVENWOOD_BUILD_PROP); + RavenwoodSystemProperties.initialize(); setSystemProperties(null); // Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()), diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java index d8f2b705d539..3ed8b0a748e1 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java @@ -16,7 +16,6 @@ package android.platform.test.ravenwood; import static android.os.Process.FIRST_APPLICATION_UID; -import static android.os.Process.SYSTEM_UID; import static android.os.UserHandle.SYSTEM; import android.annotation.NonNull; @@ -61,17 +60,14 @@ public final class RavenwoodConfig { * Unless the test author requests differently, run as "nobody", and give each collection of * tests its own unique PID. */ - int mUid = NOBODY_UID; + int mUid = FIRST_APPLICATION_UID; int mPid = sNextPid.getAndIncrement(); String mTestPackageName; String mTargetPackageName; - int mMinSdkLevel; int mTargetSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT; - boolean mProvideMainThread = false; - final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties(); final List<Class<?>> mServicesRequired = new ArrayList<>(); @@ -108,20 +104,18 @@ public final class RavenwoodConfig { } /** - * Configure the identity of this process to be the system UID for the duration of the - * test. Has no effect on non-Ravenwood environments. + * @deprecated no longer used. We always use an app UID. */ + @Deprecated public Builder setProcessSystem() { - mConfig.mUid = SYSTEM_UID; return this; } /** - * Configure the identity of this process to be an app UID for the duration of the - * test. Has no effect on non-Ravenwood environments. + * @deprecated no longer used. We always use an app UID. */ + @Deprecated public Builder setProcessApp() { - mConfig.mUid = FIRST_APPLICATION_UID; return this; } @@ -144,14 +138,6 @@ public final class RavenwoodConfig { } /** - * Configure the min SDK level of the test. - */ - public Builder setMinSdkLevel(int sdkLevel) { - mConfig.mMinSdkLevel = sdkLevel; - return this; - } - - /** * Configure the target SDK level of the test. */ public Builder setTargetSdkLevel(int sdkLevel) { @@ -160,14 +146,10 @@ public final class RavenwoodConfig { } /** - * Configure a "main" thread to be available for the duration of the test, as defined - * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments. - * - * @deprecated + * @deprecated no longer used. Main thread is always available. */ @Deprecated public Builder setProvideMainThread(boolean provideMainThread) { - mConfig.mProvideMainThread = provideMainThread; return this; } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index 3d6ac0f37050..bfa3802ce583 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -112,20 +112,18 @@ public final class RavenwoodRule implements TestRule { } /** - * Configure the identity of this process to be the system UID for the duration of the - * test. Has no effect on non-Ravenwood environments. + * @deprecated no longer used. We always use an app UID. */ + @Deprecated public Builder setProcessSystem() { - mBuilder.setProcessSystem(); return this; } /** - * Configure the identity of this process to be an app UID for the duration of the - * test. Has no effect on non-Ravenwood environments. + * @deprecated no longer used. We always use an app UID. */ + @Deprecated public Builder setProcessApp() { - mBuilder.setProcessApp(); return this; } @@ -139,14 +137,10 @@ public final class RavenwoodRule implements TestRule { } /** - * Configure a "main" thread to be available for the duration of the test, as defined - * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments. - * - * @deprecated + * @deprecated no longer used. Main thread is always available. */ @Deprecated public Builder setProvideMainThread(boolean provideMainThread) { - mBuilder.setProvideMainThread(provideMainThread); return this; } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java index 9bc45bee1775..3e4619f55c6d 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java @@ -16,21 +16,30 @@ package android.platform.test.ravenwood; -import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_SYSPROP; +import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING; +import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath; -import com.android.ravenwood.common.RavenwoodCommonUtils; +import android.util.Log; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; public class RavenwoodSystemProperties { private static final String TAG = "RavenwoodSystemProperties"; + /** We pull in propeties from this file. */ + private static final String RAVENWOOD_BUILD_PROP = "ravenwood-data/ravenwood-build.prop"; + + /** This is the actual build.prop we use to build the device (contents depends on lunch). */ + private static final String DEVICE_BUILD_PROP = "ravenwood-data/build.prop"; + + /** The default values. */ private static final Map<String, String> sDefaultValues = new HashMap<>(); private static final String[] PARTITIONS = { @@ -43,52 +52,54 @@ public class RavenwoodSystemProperties { "vendor_dlkm", }; - /** - * More info about property file loading: system/core/init/property_service.cpp - * In the following logic, the only partition we would need to consider is "system", - * since we only read from system-build.prop - */ - static void initialize(String propFile) { - // Load all properties from build.prop + private static Map<String, String> readProperties(String propFile) { + // Use an ordered map just for cleaner dump log. + final Map<String, String> ret = new LinkedHashMap<>(); try { Files.readAllLines(Path.of(propFile)).stream() .map(String::trim) .filter(s -> !s.startsWith("#")) .map(s -> s.split("\\s*=\\s*", 2)) .filter(a -> a.length == 2) - .forEach(a -> sDefaultValues.put(a[0], a[1])); + .forEach(a -> ret.put(a[0], a[1])); } catch (IOException e) { throw new RuntimeException(e); } + return ret; + } - // If ro.product.${name} is not set, derive from ro.product.${partition}.${name} - // If ro.product.cpu.abilist* is not set, derive from ro.${partition}.product.cpu.abilist* - for (var entry : Set.copyOf(sDefaultValues.entrySet())) { - final String key; - if (entry.getKey().startsWith("ro.product.system.")) { - var name = entry.getKey().substring(18); - key = "ro.product." + name; - - } else if (entry.getKey().startsWith("ro.system.product.cpu.abilist")) { - var name = entry.getKey().substring(22); - key = "ro.product.cpu." + name; + /** + * Load default sysprops from {@link #RAVENWOOD_BUILD_PROP}. We also pull in + * certain properties from the acutual device's build.prop {@link #DEVICE_BUILD_PROP} too. + * + * More info about property file loading: system/core/init/property_service.cpp + * In the following logic, the only partition we would need to consider is "system", + * since we only read from system-build.prop + */ + static void initialize() { + var path = getRavenwoodRuntimePath(); + var ravenwoodProps = readProperties(path + RAVENWOOD_BUILD_PROP); + var deviceProps = readProperties(path + DEVICE_BUILD_PROP); + + Log.i(TAG, "Default system properties:"); + ravenwoodProps.forEach((key, origValue) -> { + final String value; + + // If a value starts with "$$$", then this is a reference to the device-side value. + if (origValue.startsWith("$$$")) { + var deviceKey = origValue.substring(3); + var deviceValue = deviceProps.get(deviceKey); + if (deviceValue == null) { + throw new RuntimeException("Failed to initialize system properties. Key '" + + deviceKey + "' doesn't exist in the device side build.prop"); + } + value = deviceValue; } else { - continue; - } - if (!sDefaultValues.containsKey(key)) { - sDefaultValues.put(key, entry.getValue()); + value = origValue; } - } - - // Some other custom values - sDefaultValues.put("ro.board.first_api_level", "1"); - sDefaultValues.put("ro.product.first_api_level", "1"); - sDefaultValues.put("ro.soc.manufacturer", "Android"); - sDefaultValues.put("ro.soc.model", "Ravenwood"); - sDefaultValues.put(RAVENWOOD_SYSPROP, "1"); - - // Log all values - sDefaultValues.forEach((key, value) -> RavenwoodCommonUtils.log(TAG, key + "=" + value)); + Log.i(TAG, key + "=" + value); + sDefaultValues.put(key, value); + }); // Copy ro.product.* and ro.build.* to all partitions, just in case // We don't want to log these because these are just a lot of duplicate values @@ -104,6 +115,13 @@ public class RavenwoodSystemProperties { } } } + if (RAVENWOOD_VERBOSE_LOGGING) { + // Dump all properties for local debugging. + Log.v(TAG, "All system properties:"); + for (var key : sDefaultValues.keySet().stream().sorted().toList()) { + Log.v(TAG, "" + key + "=" + sDefaultValues.get(key)); + } + } } private volatile boolean mIsImmutable; diff --git a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java b/ravenwood/runtime-helper-src/framework/android/util/Log_host.java index d232ef2076be..c85bd23db893 100644 --- a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java +++ b/ravenwood/runtime-helper-src/framework/android/util/Log_host.java @@ -18,6 +18,7 @@ package android.util; import android.util.Log.Level; import com.android.internal.os.RuntimeInit; +import com.android.ravenwood.common.RavenwoodCommonUtils; import java.io.PrintStream; @@ -35,6 +36,9 @@ public class Log_host { } public static int println_native(int bufID, int priority, String tag, String msg) { + if (priority < Log.INFO && !RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING) { + return msg.length(); // No verbose logging. + } final String buffer; switch (bufID) { case Log.LOG_ID_MAIN: buffer = "main"; break; diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh index 1910100a7f5d..fe2269a8dc38 100755 --- a/ravenwood/scripts/run-ravenwood-tests.sh +++ b/ravenwood/scripts/run-ravenwood-tests.sh @@ -33,7 +33,7 @@ include_re="" exclude_re="" smoke_exclude_re="" dry_run="" -while getopts "sx:f:d" opt; do +while getopts "sx:f:dt" opt; do case "$opt" in s) # Remove slow tests. @@ -51,6 +51,9 @@ case "$opt" in # Dry run dry_run="echo" ;; + t) + export RAVENWOOD_LOG_OUT=$(tty) + ;; '?') exit 1 ;; diff --git a/ravenwood/texts/build.prop-sample-cuttlefish b/ravenwood/texts/build.prop-sample-cuttlefish new file mode 100644 index 000000000000..f78b727f5779 --- /dev/null +++ b/ravenwood/texts/build.prop-sample-cuttlefish @@ -0,0 +1,132 @@ +# This is file is generated with `aosp_cf_x86_64_phone-trunk_staging-eng` on 2024-11-06. +# We have this file here only as a reference. We don't actually use this file anywhere. + +#################################### +# from generate_common_build_props +# These properties identify this partition image. +#################################### +ro.product.system.brand=Android +ro.product.system.device=generic +ro.product.system.manufacturer=Android +ro.product.system.model=mainline +ro.product.system.name=mainline +ro.system.product.cpu.abilist=x86_64,x86,arm64-v8a,armeabi-v7a,armeabi +ro.system.product.cpu.abilist32=x86,armeabi-v7a,armeabi +ro.system.product.cpu.abilist64=x86_64,arm64-v8a +ro.system.build.date=Tue Nov 5 13:25:43 PST 2024 +ro.system.build.date.utc=1730841943 +ro.system.build.fingerprint=generic/aosp_cf_x86_64_phone/vsoc_x86_64:Baklava/MAIN/eng.omakot:eng/test-keys +ro.system.build.id=MAIN +ro.system.build.tags=test-keys +ro.system.build.type=eng +ro.system.build.version.incremental=eng.omakot +ro.system.build.version.release=15 +ro.system.build.version.release_or_codename=Baklava +ro.system.build.version.sdk=35 +#################################### +# from gen_build_prop.py:generate_build_info +#################################### +# begin build properties +ro.build.legacy.id=MAIN +ro.build.display.id=aosp_cf_x86_64_phone-eng Baklava MAIN eng.omakot test-keys +ro.build.version.incremental=eng.omakot +ro.build.version.sdk=35 +ro.build.version.preview_sdk=1 +ro.build.version.preview_sdk_fingerprint=2ef06129940d459014cf4dede3950d71 +ro.build.version.codename=Baklava +ro.build.version.all_codenames=Baklava +ro.build.version.known_codenames=Base,Base11,Cupcake,Donut,Eclair,Eclair01,EclairMr1,Froyo,Gingerbread,GingerbreadMr1,Honeycomb,HoneycombMr1,HoneycombMr2,IceCreamSandwich,IceCreamSandwichMr1,JellyBean,JellyBeanMr1,JellyBeanMr2,Kitkat,KitkatWatch,Lollipop,LollipopMr1,M,N,NMr1,O,OMr1,P,Q,R,S,Sv2,Tiramisu,UpsideDownCake,VanillaIceCream,Baklava +ro.build.version.release=15 +ro.build.version.release_or_codename=Baklava +ro.build.version.release_or_preview_display=Baklava +ro.build.version.security_patch=2024-08-05 +ro.build.version.base_os= +ro.build.version.min_supported_target_sdk=28 +ro.build.date=Tue Nov 5 13:25:43 PST 2024 +ro.build.date.utc=1730841943 +ro.build.type=eng +ro.build.user=omakoto +ro.build.host=omakoto-ct1.c.googlers.com +ro.build.tags=test-keys +ro.build.flavor=aosp_cf_x86_64_phone-eng +# ro.product.cpu.abi and ro.product.cpu.abi2 are obsolete, +# use ro.product.cpu.abilist instead. +ro.product.cpu.abi=x86_64 +ro.product.locale=en-US +ro.wifi.channels= +# ro.build.product is obsolete; use ro.product.device +ro.build.product=vsoc_x86_64 +# Do not try to parse description or thumbprint +ro.build.description=aosp_cf_x86_64_phone-eng Baklava MAIN eng.omakot test-keys +# end build properties +#################################### +# from variable ADDITIONAL_SYSTEM_PROPERTIES +#################################### +ro.treble.enabled=true +ro.llndk.api_level=202504 +ro.actionable_compatible_property.enabled=true +persist.debug.dalvik.vm.core_platform_api_policy=just-warn +ro.postinstall.fstab.prefix=/system +ro.kernel.android.checkjni=1 +ro.secure=0 +ro.allow.mock.location=1 +dalvik.vm.lockprof.threshold=500 +ro.debuggable=1 +dalvik.vm.image-dex2oat-filter=extract +init.svc_debug.no_fatal.zygote=true +net.bt.name=Android +ro.force.debuggable=0 +#################################### +# from variable PRODUCT_SYSTEM_PROPERTIES +#################################### +debug.atrace.tags.enableflags=0 +persist.traced.enable=1 +dalvik.vm.image-dex2oat-Xms=64m +dalvik.vm.image-dex2oat-Xmx=64m +dalvik.vm.dex2oat-Xms=64m +dalvik.vm.dex2oat-Xmx=512m +dalvik.vm.usejit=true +dalvik.vm.dexopt.secondary=true +dalvik.vm.dexopt.thermal-cutoff=2 +dalvik.vm.appimageformat=lz4 +ro.dalvik.vm.native.bridge=0 +pm.dexopt.post-boot=verify +pm.dexopt.first-boot=verify +pm.dexopt.boot-after-ota=verify +pm.dexopt.boot-after-mainline-update=verify +pm.dexopt.install=speed-profile +pm.dexopt.install-fast=skip +pm.dexopt.install-bulk=speed-profile +pm.dexopt.install-bulk-secondary=verify +pm.dexopt.install-bulk-downgraded=verify +pm.dexopt.install-bulk-secondary-downgraded=verify +pm.dexopt.bg-dexopt=speed-profile +pm.dexopt.ab-ota=speed-profile +pm.dexopt.inactive=verify +pm.dexopt.cmdline=verify +pm.dexopt.shared=speed +dalvik.vm.disable-art-service-dexopt=true +dalvik.vm.disable-odrefresh=true +dalvik.vm.dex2oat-resolve-startup-strings=true +dalvik.vm.dex2oat-max-image-block-size=524288 +dalvik.vm.minidebuginfo=true +dalvik.vm.dex2oat-minidebuginfo=true +dalvik.vm.madvise.vdexfile.size=104857600 +dalvik.vm.madvise.odexfile.size=104857600 +dalvik.vm.madvise.artfile.size=4294967295 +dalvik.vm.usap_pool_enabled=false +dalvik.vm.usap_refill_threshold=1 +dalvik.vm.usap_pool_size_max=3 +dalvik.vm.usap_pool_size_min=1 +dalvik.vm.usap_pool_refill_delay_ms=3000 +dalvik.vm.useartservice=true +dalvik.vm.enable_pr_dexopt=true +ro.cp_system_other_odex=1 +ro.apex.updatable=true +ro.launcher.depth.widget=0 +#################################### +# from variable PRODUCT_SYSTEM_DEFAULT_PROPERTIES +#################################### +# Auto-added by post_process_props.py +persist.sys.usb.config=adb +# end of file diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt index 82be2c0db24e..c196a09e18d1 100644 --- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt @@ -15,7 +15,9 @@ com.android.internal.os.BatteryStatsHistory com.android.internal.os.BatteryStatsHistoryIterator com.android.internal.os.Clock com.android.internal.os.LongArrayMultiStateCounter +com.android.internal.os.LongArrayMultiStateCounter_ravenwood com.android.internal.os.LongMultiStateCounter +com.android.internal.os.LongMultiStateCounter_ravenwood com.android.internal.os.MonotonicClock com.android.internal.os.PowerProfile com.android.internal.os.PowerStats @@ -143,6 +145,7 @@ android.os.LocaleList android.os.Looper android.os.Message android.os.MessageQueue +android.os.MessageQueue_ravenwood android.os.PackageTagsList android.os.Parcel android.os.ParcelFileDescriptor @@ -235,6 +238,7 @@ android.database.Cursor android.database.CursorIndexOutOfBoundsException android.database.CursorJoiner android.database.CursorWindow +android.database.CursorWindow_ravenwood android.database.CursorWrapper android.database.DataSetObservable android.database.DataSetObserver @@ -370,4 +374,3 @@ android.app.compat.* com.android.server.compat.* com.android.internal.compat.* android.app.AppCompatCallbacks - diff --git a/ravenwood/texts/ravenwood-build.prop b/ravenwood/texts/ravenwood-build.prop new file mode 100644 index 000000000000..93a18cffec50 --- /dev/null +++ b/ravenwood/texts/ravenwood-build.prop @@ -0,0 +1,44 @@ +# This file contains system properties used on ravenwood. + +ro.is_on_ravenwood=1 + +ro.board.first_api_level=1 +ro.product.first_api_level=1 +ro.soc.manufacturer=Android +ro.soc.model=Ravenwood +ro.debuggable=1 + +# The ones starting with "ro.product" or "ro.bild" will be copied to all "partitions" too. +# See RavenwoodSystemProperties. +ro.product.brand=Android +ro.product.device=Ravenwood +ro.product.manufacturer=Android +ro.product.model=Ravenwood +ro.product.name=Ravenwood +ro.product.cpu.abilist=x86_64 +ro.product.cpu.abilist32= +ro.product.cpu.abilist64=x86_64 + +ro.build.date=Thu Jan 01 00:00:00 GMT 2024 +ro.build.date.utc=1704092400 +ro.build.id=MAIN +ro.build.tags=dev-keys +ro.build.type=userdebug +ro.build.version.incremental=userdebug.ravenwood.20240101 + +# These are what we used to use on Ravenwood, copied here as a reference. +#ro.build.version.codename=REL +#ro.build.version.all_codenames=REL +#ro.build.version.known_codenames=REL +#ro.build.version.release=14 +#ro.build.version.release_or_codename=VanillaIceCream +#ro.build.version.sdk=34 + +# We pull in the following values from the real build.prop file. +ro.build.version.codename=$$$ro.build.version.codename +ro.build.version.all_codenames=$$$ro.build.version.codename +ro.build.version.known_codenames=$$$ro.build.version.codename +ro.build.version.release=$$$ro.build.version.release +ro.build.version.release_or_codename=$$$ro.build.version.release_or_codename +ro.build.version.release_or_preview_display=$$$ro.build.version.release_or_preview_display +ro.build.version.sdk=$$$ro.build.version.sdk diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 974cba2450f4..86d3ee6d1257 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -5071,6 +5071,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @EnforcePermission(MANAGE_ACCESSIBILITY) public boolean isAccessibilityServiceWarningRequired(AccessibilityServiceInfo info) { isAccessibilityServiceWarningRequired_enforcePermission(); + if (info == null) { + Log.e(LOG_TAG, "Called isAccessibilityServiceWarningRequired with null service info"); + return true; + } + final ComponentName componentName = info.getComponentName(); // Warning is not required if the service is already enabled. diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index 268e56487c4b..f13e22950e2d 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -29,7 +29,7 @@ import android.app.appfunctions.AppFunctionManagerHelper; import android.app.appfunctions.AppFunctionRuntimeMetadata; import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; -import android.app.appfunctions.ExecuteAppFunctionResponse; +import android.app.appfunctions.AppFunctionException; import android.app.appfunctions.IAppFunctionEnabledCallback; import android.app.appfunctions.IAppFunctionManager; import android.app.appfunctions.IAppFunctionService; @@ -156,11 +156,10 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { mCallerValidator.verifyTargetUserHandle( requestInternal.getUserHandle(), validatedCallingPackage); } catch (SecurityException exception) { - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_DENIED, - exception.getMessage(), - /* extras= */ null)); + safeExecuteAppFunctionCallback.onError( + new AppFunctionException( + AppFunctionException.ERROR_DENIED, + exception.getMessage())); return null; } @@ -180,7 +179,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { safeExecuteAppFunctionCallback, executeAppFunctionCallback.asBinder()); } catch (Exception e) { - safeExecuteAppFunctionCallback.onResult( + safeExecuteAppFunctionCallback.onError( mapExceptionToExecuteAppFunctionResponse(e)); } }); @@ -198,22 +197,19 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { UserHandle targetUser = requestInternal.getUserHandle(); // TODO(b/354956319): Add and honor the new enterprise policies. if (mCallerValidator.isUserOrganizationManaged(targetUser)) { - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, + safeExecuteAppFunctionCallback.onError( + new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR, "Cannot run on a device with a device owner or from the managed" - + " profile.", - /* extras= */ null)); + + " profile.")); return; } String targetPackageName = requestInternal.getClientRequest().getTargetPackageName(); if (TextUtils.isEmpty(targetPackageName)) { - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT, - "Target package name cannot be empty.", - /* extras= */ null)); + safeExecuteAppFunctionCallback.onError( + new AppFunctionException( + AppFunctionException.ERROR_INVALID_ARGUMENT, + "Target package name cannot be empty.")); return; } @@ -253,11 +249,10 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { mInternalServiceHelper.resolveAppFunctionService( targetPackageName, targetUser); if (serviceIntent == null) { - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, - "Cannot find the target service.", - /* extras= */ null)); + safeExecuteAppFunctionCallback.onError( + new AppFunctionException( + AppFunctionException.ERROR_SYSTEM_ERROR, + "Cannot find the target service.")); return; } bindAppFunctionServiceUnchecked( @@ -272,7 +267,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { }) .exceptionally( ex -> { - safeExecuteAppFunctionCallback.onResult( + safeExecuteAppFunctionCallback.onError( mapExceptionToExecuteAppFunctionResponse(ex)); return null; }); @@ -446,11 +441,9 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { if (!bindServiceResult) { Slog.e(TAG, "Failed to bind to the AppFunctionService"); - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, - "Failed to bind the AppFunctionService.", - /* extras= */ null)); + safeExecuteAppFunctionCallback.onError( + new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR, + "Failed to bind the AppFunctionService.")); } } @@ -459,22 +452,21 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { .getSystemService(AppSearchManager.class); } - private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) { + private AppFunctionException mapExceptionToExecuteAppFunctionResponse(Throwable e) { if (e instanceof CompletionException) { e = e.getCause(); } - int resultCode = ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR; + int resultCode = AppFunctionException.ERROR_SYSTEM_ERROR; if (e instanceof AppSearchException appSearchException) { resultCode = mapAppSearchResultFailureCodeToExecuteAppFunctionResponse( appSearchException.getResultCode()); } else if (e instanceof SecurityException) { - resultCode = ExecuteAppFunctionResponse.RESULT_DENIED; + resultCode = AppFunctionException.ERROR_DENIED; } else if (e instanceof DisabledAppFunctionException) { - resultCode = ExecuteAppFunctionResponse.RESULT_DISABLED; + resultCode = AppFunctionException.ERROR_DISABLED; } - return ExecuteAppFunctionResponse.newFailure( - resultCode, e.getMessage(), /* extras= */ null); + return new AppFunctionException(resultCode, e.getMessage()); } private int mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(int resultCode) { @@ -485,13 +477,13 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { switch (resultCode) { case AppSearchResult.RESULT_NOT_FOUND: - return ExecuteAppFunctionResponse.RESULT_FUNCTION_NOT_FOUND; + return AppFunctionException.ERROR_FUNCTION_NOT_FOUND; case AppSearchResult.RESULT_INVALID_ARGUMENT: case AppSearchResult.RESULT_INTERNAL_ERROR: case AppSearchResult.RESULT_SECURITY_ERROR: // fall-through } - return ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR; + return AppFunctionException.ERROR_SYSTEM_ERROR; } private void registerAppSearchObserver(@NonNull TargetUser user) { diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java index 129be65f3153..c689bb92f8f7 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java +++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java @@ -17,6 +17,7 @@ package com.android.server.appfunctions; import android.annotation.NonNull; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; +import android.app.appfunctions.AppFunctionException; import android.app.appfunctions.ExecuteAppFunctionResponse; import android.app.appfunctions.IAppFunctionService; import android.app.appfunctions.ICancellationCallback; @@ -57,17 +58,22 @@ public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAp mCancellationCallback, new IExecuteAppFunctionCallback.Stub() { @Override - public void onResult(ExecuteAppFunctionResponse response) { + public void onSuccess(ExecuteAppFunctionResponse response) { mSafeExecuteAppFunctionCallback.onResult(response); serviceUsageCompleteListener.onCompleted(); } + + @Override + public void onError(AppFunctionException error) { + mSafeExecuteAppFunctionCallback.onError(error); + serviceUsageCompleteListener.onCompleted(); + } }); } catch (Exception e) { - mSafeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR, - e.getMessage(), - /* extras= */ null)); + mSafeExecuteAppFunctionCallback.onError( + new AppFunctionException( + AppFunctionException.ERROR_APP_UNKNOWN_ERROR, + e.getMessage())); serviceUsageCompleteListener.onCompleted(); } } @@ -75,11 +81,9 @@ public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAp @Override public void onFailedToConnect() { Slog.e(TAG, "Failed to connect to service"); - mSafeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR, - "Failed to connect to AppFunctionService", - /* extras= */ null)); + mSafeExecuteAppFunctionCallback.onError( + new AppFunctionException(AppFunctionException.ERROR_APP_UNKNOWN_ERROR, + "Failed to connect to AppFunctionService")); } @Override diff --git a/services/art-profile b/services/art-profile index 6fa4c88cb1f6..ce1e2c6f1397 100644 --- a/services/art-profile +++ b/services/art-profile @@ -5657,7 +5657,7 @@ Lcom/android/server/utils/WatchedSparseSetArray; Lcom/android/server/utils/Watcher; Lcom/android/server/vibrator/VibratorController$NativeWrapper; Lcom/android/server/vibrator/VibratorController$OnVibrationCompleteListener; -Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener; +Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks; Lcom/android/server/vibrator/VibratorManagerService; Lcom/android/server/vr/EnabledComponentsObserver$EnabledComponentChangeListener; Lcom/android/server/vr/VrManagerService; diff --git a/services/art-wear-profile b/services/art-wear-profile index 47bdb1385137..1e3090f9bf00 100644 --- a/services/art-wear-profile +++ b/services/art-wear-profile @@ -1330,7 +1330,7 @@ Lcom/android/server/utils/WatchedSparseSetArray; Lcom/android/server/utils/Watcher; Lcom/android/server/vibrator/VibratorController$NativeWrapper; Lcom/android/server/vibrator/VibratorController$OnVibrationCompleteListener; -Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener; +Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks; Lcom/android/server/vibrator/VibratorManagerService; Lcom/android/server/vr/EnabledComponentsObserver$EnabledComponentChangeListener; Lcom/android/server/vr/VrManagerService; @@ -24948,7 +24948,7 @@ PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;-><init>()V PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->cancelSynced()V PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->getCapabilities()J PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->getVibratorIds()[I -PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->init(Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;)V +PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->init(Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;)V PLcom/android/server/vibrator/VibratorManagerService$VibrationCompleteListener;-><init>(Lcom/android/server/vibrator/VibratorManagerService;)V PLcom/android/server/vibrator/VibratorManagerService$VibrationCompleteListener;->onComplete(IJ)V PLcom/android/server/vibrator/VibratorManagerService$VibrationRecords;-><init>(II)V diff --git a/services/core/Android.bp b/services/core/Android.bp index 3ccad16073a7..7f482ac79989 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -158,6 +158,7 @@ java_library_static { "android.hardware.gnss-V2-java", "android.hardware.vibrator-V3-java", "app-compat-annotations", + "art_exported_aconfig_flags_lib", "framework-tethering.stubs.module_lib", "keepanno-annotations", "service-art.stubs.system_server", diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 78bc658d49c7..3dcca1433dec 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -22,6 +22,9 @@ import static android.os.Flags.stateOfHealthPublic; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent; import static com.android.server.health.Utils.copyV1Battery; +import static java.lang.Math.abs; + +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; @@ -48,6 +51,7 @@ import android.os.FileUtils; import android.os.Handler; import android.os.IBatteryPropertiesRegistrar; import android.os.IBinder; +import android.os.Looper; import android.os.OsProtoEnums; import android.os.PowerManager; import android.os.RemoteException; @@ -67,6 +71,7 @@ import android.util.EventLog; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.logging.MetricsLogger; import com.android.internal.os.SomeArgs; @@ -84,6 +89,7 @@ import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArraySet; /** @@ -149,19 +155,112 @@ public final class BatteryService extends SystemService { private HealthInfo mHealthInfo; private final HealthInfo mLastHealthInfo = new HealthInfo(); private boolean mBatteryLevelCritical; - private int mLastBatteryStatus; - private int mLastBatteryHealth; - private boolean mLastBatteryPresent; - private int mLastBatteryLevel; - private int mLastBatteryVoltage; - private int mLastBatteryTemperature; - private boolean mLastBatteryLevelCritical; - private int mLastMaxChargingCurrent; - private int mLastMaxChargingVoltage; - private int mLastChargeCounter; - private int mLastBatteryCycleCount; - private int mLastChargingState; - private int mLastBatteryCapacityLevel; + + /** + * {@link HealthInfo#batteryStatus} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryStatus; + /** + * {@link HealthInfo#batteryHealth} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryHealth; + /** + * {@link HealthInfo#batteryPresent} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private boolean mLastBroadcastBatteryPresent; + /** + * {@link HealthInfo#batteryLevel} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryLevel; + /** + * {@link HealthInfo#batteryVoltageMillivolts} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryVoltage; + /** + * {@link HealthInfo#batteryTemperatureTenthsCelsius} value when + * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryTemperature; + /** + * {@link #mBatteryLevelCritical} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: These values may be used for internal operations and/or to determine whether to trigger + * the broadcast or not. + */ + private boolean mLastBroadcastBatteryLevelCritical; + /** + * {@link HealthInfo#maxChargingCurrentMicroamps} value when + * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastMaxChargingCurrent; + /** + * {@link HealthInfo#maxChargingVoltageMicrovolts} value when + * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastMaxChargingVoltage; + /** + * {@link HealthInfo#batteryChargeCounterUah} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastChargeCounter; + /** + * {@link HealthInfo#batteryCycleCount} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryCycleCount; + /** + * {@link HealthInfo#chargingState} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastChargingState; + /** + * {@link HealthInfo#batteryCapacityLevel} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryCapacityLevel; + /** + * {@link #mPlugType} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: These values may be used for internal operations and/or to determine whether to trigger + * the broadcast or not. + */ + private int mLastBroadcastPlugType = -1; // Extra state so we can detect first run + /** + * {@link #mInvalidCharger} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: These values may be used for internal operations and/or to determine whether to trigger + * the broadcast or not. + */ + private int mLastBroadcastInvalidCharger; /** * The last seen charging policy. This requires the * {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be @@ -172,7 +271,6 @@ public final class BatteryService extends SystemService { private int mSequence = 1; private int mInvalidCharger; - private int mLastInvalidCharger; private int mLowBatteryWarningLevel; private int mLastLowBatteryWarningLevel; @@ -184,7 +282,6 @@ public final class BatteryService extends SystemService { private static String sSystemUiPackage; private int mPlugType; - private int mLastPlugType = -1; // Extra state so we can detect first run private boolean mBatteryLevelLow; @@ -197,6 +294,16 @@ public final class BatteryService extends SystemService { private boolean mUpdatesStopped; private boolean mBatteryInputSuspended; + /** + * Time when the voltage was updated last by HAL and we sent the + * {@link Intent#ACTION_BATTERY_CHANGED} broadcast. + * Note: This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast + * so it is possible that voltage was updated but we did not send the broadcast so in that + * case we do not update the time. + */ + @VisibleForTesting + public long mLastBroadcastVoltageUpdateTime; + private Led mLed; private boolean mSentLowBatteryBroadcast = false; @@ -211,7 +318,8 @@ public final class BatteryService extends SystemService { private final CopyOnWriteArraySet<BatteryManagerInternal.ChargingPolicyChangeListener> mChargingPolicyChangeListeners = new CopyOnWriteArraySet<>(); - private static final Bundle BATTERY_CHANGED_OPTIONS = BroadcastOptions.makeBasic() + @VisibleForTesting + public static final Bundle BATTERY_CHANGED_OPTIONS = BroadcastOptions.makeBasic() .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) .toBundle(); @@ -234,6 +342,25 @@ public final class BatteryService extends SystemService { private static final int MSG_BROADCAST_POWER_CONNECTION_CHANGED = 2; private static final int MSG_BROADCAST_BATTERY_LOW_OKAY = 3; + /** + * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We + * only send the broadcast and update the temperature value when the temp change is greater or + * equals to 1 degree celsius. + */ + private static final int ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE = 10; + /** + * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We + * only send the broadcast if the last voltage was updated at least 20s seconds back and has a + * fluctuation of at least 1%. + */ + private static final int TIME_DIFF_FOR_VOLTAGE_UPDATE_MS = 20000; + /** + * The value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We + * only send the broadcast if the last voltage was updated at least 20s seconds back and has a + * fluctuation of at least 1%. + */ + private static final float BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE = 0.01f; + private final Handler.Callback mLocalCallback = msg -> { switch (msg.what) { case MSG_BROADCAST_BATTERY_CHANGED: { @@ -283,10 +410,19 @@ public final class BatteryService extends SystemService { }; public BatteryService(Context context) { + this(context, Objects.requireNonNull(Looper.myLooper(), + "BatteryService uses handler!! Can't create handler inside thread that has not " + + "called Looper.prepare()")); + } + + @VisibleForTesting + public BatteryService(Context context, @NonNull Looper looper) { super(context); + Objects.requireNonNull(looper); + mContext = context; - mHandler = new Handler(mLocalCallback, true /*async*/); + mHandler = new Handler(looper, mLocalCallback, true /*async*/); mLed = new Led(context, getLocalService(LightsManager.class)); mBatteryStats = BatteryStatsService.getService(); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); @@ -436,7 +572,7 @@ public final class BatteryService extends SystemService { private boolean shouldSendBatteryLowLocked() { final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE; - final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE; + final boolean oldPlugged = mLastBroadcastPlugType != BATTERY_PLUGGED_NONE; /* The ACTION_BATTERY_LOW broadcast is sent in these situations: * - is just un-plugged (previously was plugged) and battery level is @@ -447,7 +583,7 @@ public final class BatteryService extends SystemService { return !plugged && mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel - && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel + && (oldPlugged || mLastBroadcastBatteryLevel > mLowBatteryWarningLevel || mHealthInfo.batteryLevel > mLastLowBatteryWarningLevel); } @@ -515,7 +651,13 @@ public final class BatteryService extends SystemService { } } - private void update(android.hardware.health.HealthInfo info) { + /** + * Updates the healthInfo and triggers the broadcast. + * + * @param info the new health info + */ + @VisibleForTesting + public void update(android.hardware.health.HealthInfo info) { traceBegin("HealthInfoUpdate"); Trace.traceCounter( @@ -556,8 +698,8 @@ public final class BatteryService extends SystemService { long dischargeDuration = 0; mBatteryLevelCritical = - mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN - && mHealthInfo.batteryLevel <= mCriticalBatteryLevel; + mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN + && mHealthInfo.batteryLevel <= mCriticalBatteryLevel; mPlugType = plugType(mHealthInfo); if (DEBUG) { @@ -591,24 +733,28 @@ public final class BatteryService extends SystemService { mHandler.post(this::notifyChargingPolicyChanged); } + final boolean includeChargeCounter = + !com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast() + && mHealthInfo.batteryChargeCounterUah != mLastBroadcastChargeCounter; + if (force - || (mHealthInfo.batteryStatus != mLastBatteryStatus - || mHealthInfo.batteryHealth != mLastBatteryHealth - || mHealthInfo.batteryPresent != mLastBatteryPresent - || mHealthInfo.batteryLevel != mLastBatteryLevel - || mPlugType != mLastPlugType - || mHealthInfo.batteryVoltageMillivolts != mLastBatteryVoltage - || mHealthInfo.batteryTemperatureTenthsCelsius != mLastBatteryTemperature - || mHealthInfo.maxChargingCurrentMicroamps != mLastMaxChargingCurrent - || mHealthInfo.maxChargingVoltageMicrovolts != mLastMaxChargingVoltage - || mHealthInfo.batteryChargeCounterUah != mLastChargeCounter - || mInvalidCharger != mLastInvalidCharger - || mHealthInfo.batteryCycleCount != mLastBatteryCycleCount - || mHealthInfo.chargingState != mLastChargingState - || mHealthInfo.batteryCapacityLevel != mLastBatteryCapacityLevel)) { - - if (mPlugType != mLastPlugType) { - if (mLastPlugType == BATTERY_PLUGGED_NONE) { + || (mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus + || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth + || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent + || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel + || mPlugType != mLastBroadcastPlugType + || mHealthInfo.batteryVoltageMillivolts != mLastBroadcastBatteryVoltage + || mHealthInfo.batteryTemperatureTenthsCelsius != mLastBroadcastBatteryTemperature + || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent + || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage + || includeChargeCounter + || mInvalidCharger != mLastBroadcastInvalidCharger + || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount + || mHealthInfo.chargingState != mLastBroadcastChargingState + || mHealthInfo.batteryCapacityLevel != mLastBroadcastBatteryCapacityLevel)) { + + if (mPlugType != mLastBroadcastPlugType) { + if (mLastBroadcastPlugType == BATTERY_PLUGGED_NONE) { // discharging -> charging mChargeStartLevel = mHealthInfo.batteryLevel; mChargeStartTime = SystemClock.elapsedRealtime(); @@ -622,7 +768,8 @@ public final class BatteryService extends SystemService { // There's no value in this data unless we've discharged at least once and the // battery level has changed; so don't log until it does. - if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.batteryLevel) { + if (mDischargeStartTime != 0 + && mDischargeStartLevel != mHealthInfo.batteryLevel) { dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime; logOutlier = true; EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration, @@ -639,7 +786,7 @@ public final class BatteryService extends SystemService { if (mChargeStartTime != 0 && chargeDuration != 0) { final LogMaker builder = new LogMaker(MetricsEvent.ACTION_CHARGE); builder.setType(MetricsEvent.TYPE_DISMISS); - builder.addTaggedData(MetricsEvent.FIELD_PLUG_TYPE, mLastPlugType); + builder.addTaggedData(MetricsEvent.FIELD_PLUG_TYPE, mLastBroadcastPlugType); builder.addTaggedData(MetricsEvent.FIELD_CHARGING_DURATION_MILLIS, chargeDuration); builder.addTaggedData(MetricsEvent.FIELD_BATTERY_LEVEL_START, @@ -651,19 +798,20 @@ public final class BatteryService extends SystemService { mChargeStartTime = 0; } } - if (mHealthInfo.batteryStatus != mLastBatteryStatus || - mHealthInfo.batteryHealth != mLastBatteryHealth || - mHealthInfo.batteryPresent != mLastBatteryPresent || - mPlugType != mLastPlugType) { + if (mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus + || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth + || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent + || mPlugType != mLastBroadcastPlugType) { EventLog.writeEvent(EventLogTags.BATTERY_STATUS, - mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mHealthInfo.batteryPresent ? 1 : 0, + mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, + mHealthInfo.batteryPresent ? 1 : 0, mPlugType, mHealthInfo.batteryTechnology); SystemProperties.set( "debug.tracing.battery_status", Integer.toString(mHealthInfo.batteryStatus)); SystemProperties.set("debug.tracing.plug_type", Integer.toString(mPlugType)); } - if (mHealthInfo.batteryLevel != mLastBatteryLevel) { + if (mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel) { // Don't do this just from voltage or temperature changes, that is // too noisy. EventLog.writeEvent( @@ -672,8 +820,8 @@ public final class BatteryService extends SystemService { mHealthInfo.batteryVoltageMillivolts, mHealthInfo.batteryTemperatureTenthsCelsius); } - if (mBatteryLevelCritical && !mLastBatteryLevelCritical && - mPlugType == BATTERY_PLUGGED_NONE) { + if (mBatteryLevelCritical && !mLastBroadcastBatteryLevelCritical + && mPlugType == BATTERY_PLUGGED_NONE) { // We want to make sure we log discharge cycle outliers // if the battery is about to die. dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime; @@ -684,7 +832,7 @@ public final class BatteryService extends SystemService { // Should we now switch in to low battery mode? if (mPlugType == BATTERY_PLUGGED_NONE && mHealthInfo.batteryStatus != - BatteryManager.BATTERY_STATUS_UNKNOWN + BatteryManager.BATTERY_STATUS_UNKNOWN && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel) { mBatteryLevelLow = true; } @@ -692,7 +840,7 @@ public final class BatteryService extends SystemService { // Should we now switch out of low battery mode? if (mPlugType != BATTERY_PLUGGED_NONE) { mBatteryLevelLow = false; - } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) { + } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) { mBatteryLevelLow = false; } else if (force && mHealthInfo.batteryLevel >= mLowBatteryWarningLevel) { // If being forced, the previous state doesn't matter, we will just @@ -706,7 +854,7 @@ public final class BatteryService extends SystemService { // Separate broadcast is sent for power connected / not connected // since the standard intent will not wake any applications and some // applications may want to have smart behavior based on this. - if (mPlugType != 0 && mLastPlugType == 0) { + if (mPlugType != 0 && mLastBroadcastPlugType == 0) { final Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED); statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence); @@ -726,8 +874,7 @@ public final class BatteryService extends SystemService { } }); } - } - else if (mPlugType == 0 && mLastPlugType != 0) { + } else if (mPlugType == 0 && mLastBroadcastPlugType != 0) { final Intent statusIntent = new Intent(Intent.ACTION_POWER_DISCONNECTED); statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence); @@ -797,8 +944,14 @@ public final class BatteryService extends SystemService { // We are doing this after sending the above broadcasts, so anything processing // them will get the new sequence number at that point. (See for example how testing // of JobScheduler's BatteryController works.) - sendBatteryChangedIntentLocked(force); - if (mLastBatteryLevel != mHealthInfo.batteryLevel || mLastPlugType != mPlugType) { + + boolean rateLimitBatteryChangedBroadcast = rateLimitBatteryChangedBroadcast(force); + + if (!rateLimitBatteryChangedBroadcast) { + sendBatteryChangedIntentLocked(force); + } + if (mLastBroadcastBatteryLevel != mHealthInfo.batteryLevel + || mLastBroadcastPlugType != mPlugType) { sendBatteryLevelChangedIntentLocked(); } @@ -811,21 +964,24 @@ public final class BatteryService extends SystemService { logOutlierLocked(dischargeDuration); } - mLastBatteryStatus = mHealthInfo.batteryStatus; - mLastBatteryHealth = mHealthInfo.batteryHealth; - mLastBatteryPresent = mHealthInfo.batteryPresent; - mLastBatteryLevel = mHealthInfo.batteryLevel; - mLastPlugType = mPlugType; - mLastBatteryVoltage = mHealthInfo.batteryVoltageMillivolts; - mLastBatteryTemperature = mHealthInfo.batteryTemperatureTenthsCelsius; - mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrentMicroamps; - mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltageMicrovolts; - mLastChargeCounter = mHealthInfo.batteryChargeCounterUah; - mLastBatteryLevelCritical = mBatteryLevelCritical; - mLastInvalidCharger = mInvalidCharger; - mLastBatteryCycleCount = mHealthInfo.batteryCycleCount; - mLastChargingState = mHealthInfo.chargingState; - mLastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel; + // Only update the values when we send the broadcast + if (!rateLimitBatteryChangedBroadcast) { + mLastBroadcastBatteryStatus = mHealthInfo.batteryStatus; + mLastBroadcastBatteryHealth = mHealthInfo.batteryHealth; + mLastBroadcastBatteryPresent = mHealthInfo.batteryPresent; + mLastBroadcastBatteryLevel = mHealthInfo.batteryLevel; + mLastBroadcastPlugType = mPlugType; + mLastBroadcastBatteryVoltage = mHealthInfo.batteryVoltageMillivolts; + mLastBroadcastBatteryTemperature = mHealthInfo.batteryTemperatureTenthsCelsius; + mLastBroadcastMaxChargingCurrent = mHealthInfo.maxChargingCurrentMicroamps; + mLastBroadcastMaxChargingVoltage = mHealthInfo.maxChargingVoltageMicrovolts; + mLastBroadcastChargeCounter = mHealthInfo.batteryChargeCounterUah; + mLastBroadcastBatteryLevelCritical = mBatteryLevelCritical; + mLastBroadcastInvalidCharger = mInvalidCharger; + mLastBroadcastBatteryCycleCount = mHealthInfo.batteryCycleCount; + mLastBroadcastChargingState = mHealthInfo.chargingState; + mLastBroadcastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel; + } } } @@ -1089,6 +1245,74 @@ public final class BatteryService extends SystemService { } } + /** + * Rate limit's the broadcast based on the changes in temp, voltage and chargeCounter. + */ + private boolean rateLimitBatteryChangedBroadcast(boolean forceUpdate) { + if (!com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()) { + return false; + } + if (mLastBroadcastBatteryVoltage == 0 || mLastBroadcastBatteryTemperature == 0) { + mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime(); + return false; + } + + final boolean voltageUpdated = + mLastBroadcastBatteryVoltage != mHealthInfo.batteryVoltageMillivolts; + final boolean temperatureUpdated = + mLastBroadcastBatteryTemperature != mHealthInfo.batteryTemperatureTenthsCelsius; + final boolean otherStatesUpdated = forceUpdate + || mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus + || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth + || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent + || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel + || mPlugType != mLastBroadcastPlugType + || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent + || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage + || mInvalidCharger != mLastBroadcastInvalidCharger + || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount + || mHealthInfo.chargingState != mLastBroadcastChargingState + || mHealthInfo.batteryCapacityLevel != mLastBroadcastBatteryCapacityLevel; + + // We only rate limit based on changes in the temp, voltage. + if (otherStatesUpdated) { + + if (voltageUpdated) { + mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime(); + } + return false; + } + + final float basePointDiff = + (float) (mLastBroadcastBatteryVoltage - mHealthInfo.batteryVoltageMillivolts) + / mLastBroadcastBatteryVoltage; + + // We only send the broadcast if voltage change is greater than 1% and last voltage + // update was sent at least 20 seconds back. + if (voltageUpdated + && abs(basePointDiff) >= BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE + && SystemClock.elapsedRealtime() - mLastBroadcastVoltageUpdateTime + >= TIME_DIFF_FOR_VOLTAGE_UPDATE_MS) { + mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime(); + + return false; + } + + // Only send the broadcast if the temperature update is greater than 1 degree celsius. + if (temperatureUpdated + && abs( + mLastBroadcastBatteryTemperature - mHealthInfo.batteryTemperatureTenthsCelsius) + >= ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE) { + + if (voltageUpdated) { + mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime(); + } + return false; + } + + return true; + } + class Shell extends ShellCommand { @Override public int onCommand(String cmd) { @@ -1399,6 +1623,10 @@ public final class BatteryService extends SystemService { pw.println(" level: " + mHealthInfo.batteryLevel); pw.println(" scale: " + BATTERY_SCALE); pw.println(" voltage: " + mHealthInfo.batteryVoltageMillivolts); + pw.println(" Time when the latest updated value of the voltage was sent via " + + "battery changed broadcast: " + mLastBroadcastVoltageUpdateTime); + pw.println(" The last voltage value sent via the battery changed broadcast: " + + mLastBroadcastBatteryVoltage); pw.println(" temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius); pw.println(" technology: " + mHealthInfo.batteryTechnology); pw.println(" Charging state: " + mHealthInfo.chargingState); @@ -1457,6 +1685,11 @@ public final class BatteryService extends SystemService { Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } + @VisibleForTesting + public Handler getHandlerForTest() { + return mHandler; + } + @SuppressLint("AndroidFrameworkRequiresPermission") private static void sendBroadcastToAllUsers(Context context, Intent intent, Bundle options) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b5dcdb1ac287..18e8abb6e884 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1101,7 +1101,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProfilingServiceHelper.getInstance().onProfilingTriggerOccurred( startInfo.getRealUid(), startInfo.getPackageName(), - ProfilingTrigger.TRIGGER_TYPE_APP_COLD_START_ACTIVITY); + ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN); } } }; @@ -19307,31 +19307,18 @@ public class ActivityManagerService extends IActivityManager.Stub public void addCreatorToken(@Nullable Intent intent, String creatorPackage) { if (!preventIntentRedirect()) return; - if (intent == null || intent.getExtraIntentKeys() == null) return; - for (String key : intent.getExtraIntentKeys()) { - try { - Intent extraIntent = intent.getParcelableExtra(key, Intent.class); - if (extraIntent == null) { - Slog.w(TAG, "The key {" + key - + "} does not correspond to an intent in the extra bundle."); - continue; - } - IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent, - creatorPackage); - if (creatorToken != null) { - extraIntent.setCreatorToken(creatorToken); - Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: " - + creatorPackage + "; intent: " + intent); - FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED, - creatorToken.getCreatorUid()); - } - } catch (Exception e) { - Slog.wtf(TAG, - "Something went wrong when trying to add creator token for embedded " - + "intents of intent: ." - + intent, e); + if (intent == null) return; + intent.forEachNestedCreatorToken(extraIntent -> { + IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent, creatorPackage); + if (creatorToken != null) { + extraIntent.setCreatorToken(creatorToken); + // TODO remove Slog.wtf once proven FrameworkStatsLog works. b/375396329 + Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: " + + creatorPackage + "; intent: " + intent); + FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED, + creatorToken.getCreatorUid()); } - } + }); } private IntentCreatorToken createIntentCreatorToken(Intent intent, String creatorPackage) { diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index aa9ac6c36784..2eb9f3cb600f 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -124,6 +124,7 @@ import com.android.server.LocalServices; import com.android.server.Watchdog; import com.android.server.net.BaseNetworkObserver; import com.android.server.pm.UserManagerInternal; +import com.android.server.power.feature.PowerManagerFlags; import com.android.server.power.optimization.Flags; import com.android.server.power.stats.BatteryExternalStatsWorker; import com.android.server.power.stats.BatteryStatsDumpHelperImpl; @@ -195,6 +196,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub private final BatteryStats.BatteryStatsDumpHelper mDumpHelper; private final PowerStatsUidResolver mPowerStatsUidResolver = new PowerStatsUidResolver(); private final PowerAttributor mPowerAttributor; + private final PowerManagerFlags mPowerManagerFlags = new PowerManagerFlags(); private volatile boolean mMonitorEnabled = true; private boolean mRailsStatsCollectionEnabled = true; @@ -617,6 +619,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub BatteryConsumer.POWER_COMPONENT_ANY, Flags.streamlinedMiscBatteryStats()); + mStats.setMoveWscLoggingToNotifierEnabled( + mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled()); + mWorker.systemServicesReady(); mStats.systemServicesReady(mContext); mCpuWakeupStats.systemServicesReady(); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 08632fe09b19..c0676623a1e9 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -514,27 +514,11 @@ public class OomAdjuster { mLogger = new OomAdjusterDebugLogger(this, mService.mConstants); mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> { - final int pid = msg.arg1; - final int group = msg.arg2; - if (pid == ActivityManagerService.MY_PID) { - // Skip setting the process group for system_server, keep it as default. - return true; - } - final boolean traceEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER); - if (traceEnabled) { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setProcessGroup " - + msg.obj + " to " + group); - } - try { - android.os.Process.setProcessGroup(pid, group); - } catch (Exception e) { - if (DEBUG_ALL) { - Slog.w(TAG, "Failed setting process group of " + pid + " to " + group, e); - } - } finally { - if (traceEnabled) { - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } + final int group = msg.what; + final ProcessRecord app = (ProcessRecord) msg.obj; + setProcessGroup(app.getPid(), group, app.processName); + if (Flags.phantomProcessesFix()) { + mService.mPhantomProcessList.setProcessGroupForPhantomProcessOfApp(app, group); } return true; }); @@ -545,8 +529,31 @@ public class OomAdjuster { } void setProcessGroup(int pid, int group, String processName) { + if (pid == ActivityManagerService.MY_PID) { + // Skip setting the process group for system_server, keep it as default. + return; + } + final boolean traceEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER); + if (traceEnabled) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setProcessGroup " + + processName + " to " + group); + } + try { + android.os.Process.setProcessGroup(pid, group); + } catch (Exception e) { + if (DEBUG_ALL) { + Slog.w(TAG, "Failed setting process group of " + pid + " to " + group, e); + } + } finally { + if (traceEnabled) { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + } + } + + void setAppAndChildProcessGroup(ProcessRecord app, int group) { mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage( - 0 /* unused */, pid, group, processName)); + group, app)); } void initSettings() { @@ -3503,8 +3510,7 @@ public class OomAdjuster { processGroup = THREAD_GROUP_DEFAULT; break; } - setProcessGroup(app.getPid(), processGroup, app.processName); - mService.mPhantomProcessList.setProcessGroupForPhantomProcessOfApp(app, processGroup); + setAppAndChildProcessGroup(app, processGroup); try { final int renderThreadTid = app.getRenderThreadTid(); if (curSchedGroup == SCHED_GROUP_TOP_APP) { diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java index bfdced77a1b3..123780fb7567 100644 --- a/services/core/java/com/android/server/am/PhantomProcessList.java +++ b/services/core/java/com/android/server/am/PhantomProcessList.java @@ -548,6 +548,7 @@ public final class PhantomProcessList { */ void setProcessGroupForPhantomProcessOfApp(final ProcessRecord app, final int group) { synchronized (mLock) { + lookForPhantomProcessesLocked(app); final SparseArray<PhantomProcessRecord> array = getPhantomProcessOfAppLocked(app); if (array == null) { return; diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 7b4d6c7fff82..5d5b35b8c4fb 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -250,4 +250,14 @@ flag { is_fixed_read_only: true description: "Add +X to the prev scores according to their positions in the process LRU list" bug: "359912586" -}
\ No newline at end of file +} + +flag { + name: "phantom_processes_fix" + namespace: "backstage_power" + description: "Make sure setProcessGroupForPhantomProcessOfApp deals with phantom processes properly" + bug: "375058190" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 4ad7c10a1444..d2c044fdbb5e 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -255,6 +255,11 @@ final class DisplayDeviceInfo { public static final int DIFF_MODE_ID = 1 << 7; /** + * Diff result: The frame rate override list differs. + */ + public static final int DIFF_FRAME_RATE_OVERRIDE = 1 << 8; + + /** * Diff result: Catch-all for "everything changed" */ public static final int DIFF_EVERYTHING = 0XFFFFFFFF; @@ -523,6 +528,9 @@ final class DisplayDeviceInfo { if (modeId != other.modeId) { diff |= DIFF_MODE_ID; } + if (!Arrays.equals(frameRateOverrides, other.frameRateOverrides)) { + diff |= DIFF_FRAME_RATE_OVERRIDE; + } if (!Objects.equals(name, other.name) || !Objects.equals(uniqueId, other.uniqueId) || width != other.width @@ -546,7 +554,6 @@ final class DisplayDeviceInfo { || !Objects.equals(deviceProductInfo, other.deviceProductInfo) || ownerUid != other.ownerUid || !Objects.equals(ownerPackageName, other.ownerPackageName) - || !Arrays.equals(frameRateOverrides, other.frameRateOverrides) || !BrightnessSynchronizer.floatEquals(brightnessMinimum, other.brightnessMinimum) || !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum) || !BrightnessSynchronizer.floatEquals(brightnessDefault, diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java index 086f8a94d9b8..5f7bc4effa1b 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java +++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java @@ -27,6 +27,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.display.DisplayManagerService.SyncRoot; import com.android.server.display.utils.DebugUtils; +import java.util.Arrays; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -177,18 +178,22 @@ class DisplayDeviceRepository implements DisplayAdapter.Listener { "handleDisplayDeviceChanged"); } int diff = device.mDebugLastLoggedDeviceInfo.diff(info); - if (diff == DisplayDeviceInfo.DIFF_STATE) { + if (diff == 0) { + Slog.i(TAG, "Display device same: " + info); + } else if (diff == DisplayDeviceInfo.DIFF_STATE) { Slog.i(TAG, "Display device changed state: \"" + info.name + "\", " + Display.stateToString(info.state)); } else if (diff == DisplayDeviceInfo.DIFF_ROTATION) { Slog.i(TAG, "Display device rotated: \"" + info.name + "\", " + Surface.rotationToString(info.rotation)); - } else if (diff - == (DisplayDeviceInfo.DIFF_MODE_ID | DisplayDeviceInfo.DIFF_RENDER_TIMINGS)) { + } else if ((diff & + (DisplayDeviceInfo.DIFF_MODE_ID | DisplayDeviceInfo.DIFF_RENDER_TIMINGS + | DisplayDeviceInfo.DIFF_FRAME_RATE_OVERRIDE)) != 0) { Slog.i(TAG, "Display device changed render timings: \"" + info.name + "\", renderFrameRate=" + info.renderFrameRate + ", presentationDeadlineNanos=" + info.presentationDeadlineNanos - + ", appVsyncOffsetNanos=" + info.appVsyncOffsetNanos); + + ", appVsyncOffsetNanos=" + info.appVsyncOffsetNanos + + ", frameRateOverrides=" + Arrays.toString(info.frameRateOverrides)); } else if (diff == DisplayDeviceInfo.DIFF_COMMITTED_STATE) { if (DEBUG) { Slog.i(TAG, "Display device changed committed state: \"" + info.name diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index f5a75c7d1c38..5a2610b00772 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -25,7 +25,7 @@ import static android.Manifest.permission.MANAGE_DISPLAYS; import static android.Manifest.permission.RESTRICT_DISPLAY_MODES; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; -import static android.hardware.display.DisplayManager.EventFlag; +import static android.hardware.display.DisplayManagerGlobal.InternalEventFlag; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; @@ -96,6 +96,7 @@ import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayGroupListener; import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener; +import android.hardware.display.DisplayTopology; import android.hardware.display.DisplayViewport; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; @@ -118,6 +119,7 @@ import android.os.IBinder.DeathRecipient; import android.os.IThermalService; import android.os.Looper; import android.os.Message; +import android.os.PermissionEnforcer; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -1390,16 +1392,16 @@ public final class DisplayManagerService extends SystemService { } private void registerCallbackInternal(IDisplayManagerCallback callback, int callingPid, - int callingUid, @EventFlag long eventFlagsMask) { + int callingUid, @InternalEventFlag long internalEventFlagsMask) { synchronized (mSyncRoot) { CallbackRecord record = mCallbacks.get(callingPid); if (record != null) { - record.updateEventFlagsMask(eventFlagsMask); + record.updateEventFlagsMask(internalEventFlagsMask); return; } - record = new CallbackRecord(callingPid, callingUid, callback, eventFlagsMask); + record = new CallbackRecord(callingPid, callingUid, callback, internalEventFlagsMask); try { IBinder binder = callback.asBinder(); binder.linkToDeath(record, 0); @@ -4009,7 +4011,7 @@ public final class DisplayManagerService extends SystemService { public final int mPid; public final int mUid; private final IDisplayManagerCallback mCallback; - private @DisplayManager.EventFlag AtomicLong mEventFlagsMask; + private @InternalEventFlag AtomicLong mInternalEventFlagsMask; private final String mPackageName; public boolean mWifiDisplayScanRequested; @@ -4030,11 +4032,11 @@ public final class DisplayManagerService extends SystemService { private boolean mFrozen; CallbackRecord(int pid, int uid, @NonNull IDisplayManagerCallback callback, - @EventFlag long eventFlagsMask) { + @InternalEventFlag long internalEventFlagsMask) { mPid = pid; mUid = uid; mCallback = callback; - mEventFlagsMask = new AtomicLong(eventFlagsMask); + mInternalEventFlagsMask = new AtomicLong(internalEventFlagsMask); mCached = false; mFrozen = false; @@ -4056,8 +4058,8 @@ public final class DisplayManagerService extends SystemService { mPackageName = packageNames == null ? null : packageNames[0]; } - public void updateEventFlagsMask(@EventFlag long eventFlag) { - mEventFlagsMask.set(eventFlag); + public void updateEventFlagsMask(@InternalEventFlag long internalEventFlag) { + mInternalEventFlagsMask.set(internalEventFlag); } /** @@ -4121,13 +4123,13 @@ public final class DisplayManagerService extends SystemService { if (!shouldSendEvent(event)) { if (extraLogging(mPackageName)) { Slog.i(TAG, - "Not sending displayEvent: " + event + " due to flag:" - + mEventFlagsMask); + "Not sending displayEvent: " + event + " due to mask:" + + mInternalEventFlagsMask); } if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) { Trace.instant(Trace.TRACE_TAG_POWER, - "notifyDisplayEventAsync#notSendingEvent=" + event + ",mEventsFlag=" - + mEventFlagsMask); + "notifyDisplayEventAsync#notSendingEvent=" + event + + ",mInternalEventFlagsMask=" + mInternalEventFlagsMask); } // The client is not interested in this event, so do nothing. return true; @@ -4173,22 +4175,29 @@ public final class DisplayManagerService extends SystemService { * Return true if the client is interested in this event. */ private boolean shouldSendEvent(@DisplayEvent int event) { - final long flag = mEventFlagsMask.get(); + final long mask = mInternalEventFlagsMask.get(); switch (event) { case DisplayManagerGlobal.EVENT_DISPLAY_ADDED: - return (flag & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0; + return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED) != 0; case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED: - return (flag & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0; + return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED) != 0; case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED: - return (flag & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0; + return (mask + & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED) + != 0; case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED: - return (flag & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0; + return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED) != 0; case DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED: - return (flag & DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0; + return (mask + & DisplayManagerGlobal + .INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED) + != 0; case DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED: // fallthrough case DisplayManagerGlobal.EVENT_DISPLAY_DISCONNECTED: - return (flag & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0; + return (mask + & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) + != 0; default: // This should never happen. Slog.e(TAG, "Unknown display event " + event); @@ -4314,6 +4323,10 @@ public final class DisplayManagerService extends SystemService { @VisibleForTesting final class BinderService extends IDisplayManager.Stub { + BinderService() { + super(PermissionEnforcer.fromContext(getContext())); + } + /** * Returns information about the specified logical display. * @@ -4374,15 +4387,16 @@ public final class DisplayManagerService extends SystemService { @Override // Binder call public void registerCallback(IDisplayManagerCallback callback) { - registerCallbackWithEventMask(callback, DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); + registerCallbackWithEventMask(callback, + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED); } @Override // Binder call @SuppressLint("AndroidFrameworkRequiresPermission") // Permission only required sometimes public void registerCallbackWithEventMask(IDisplayManagerCallback callback, - @EventFlag long eventFlagsMask) { + @InternalEventFlag long internalEventFlagsMask) { if (callback == null) { throw new IllegalArgumentException("listener must not be null"); } @@ -4391,7 +4405,9 @@ public final class DisplayManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); if (mFlags.isConnectedDisplayManagementEnabled()) { - if ((eventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) { + if ((internalEventFlagsMask + & DisplayManagerGlobal + .INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) { mContext.enforceCallingOrSelfPermission(MANAGE_DISPLAYS, "Permission required to get signals about connection events."); } @@ -4399,7 +4415,7 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { - registerCallbackInternal(callback, callingPid, callingUid, eventFlagsMask); + registerCallbackInternal(callback, callingPid, callingUid, internalEventFlagsMask); } finally { Binder.restoreCallingIdentity(token); } @@ -5192,6 +5208,25 @@ public final class DisplayManagerService extends SystemService { } return ddc.getDefaultDozeBrightness(); } + + @EnforcePermission(MANAGE_DISPLAYS) + @Override // Binder call + public DisplayTopology getDisplayTopology() { + getDisplayTopology_enforcePermission(); + if (mDisplayTopologyCoordinator == null) { + return null; + } + return mDisplayTopologyCoordinator.getTopology(); + } + + @EnforcePermission(MANAGE_DISPLAYS) + @Override // Binder call + public void setDisplayTopology(DisplayTopology topology) { + setDisplayTopology_enforcePermission(); + if (mDisplayTopologyCoordinator != null) { + mDisplayTopologyCoordinator.setTopology(topology); + } + } } @VisibleForTesting diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index a9ed0aaf324b..c90dfbf5456e 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1595,7 +1595,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Note throttling effectively changes the allowed brightness range, so, similarly to HBM, // we broadcast this change through setting. final float unthrottledBrightnessState = rawBrightnessState; - DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest, + DisplayBrightnessState clampedState = mBrightnessClamperController.clamp( + displayBrightnessState, mPowerRequest, brightnessState, slowChange, /* displayState= */ state); brightnessState = clampedState.getBrightness(); slowChange = clampedState.isSlowChange(); @@ -2003,7 +2004,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mCachedBrightnessInfo.brightnessMax.value, mCachedBrightnessInfo.hbmMode.value, mCachedBrightnessInfo.hbmTransitionPoint.value, - mCachedBrightnessInfo.brightnessMaxReason.value); + mCachedBrightnessInfo.brightnessMaxReason.value, + mCachedBrightnessInfo.brightnessReason.value + == BrightnessReason.REASON_OVERRIDE); } } @@ -2028,6 +2031,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call @BrightnessInfo.BrightnessMaxReason int maxReason = state != null ? state.getBrightnessMaxReason() : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + BrightnessReason brightnessReason = state != null ? state.getBrightnessReason() + : new BrightnessReason(BrightnessReason.REASON_UNKNOWN); final float minBrightness = Math.max(stateMin, Math.min( mBrightnessRangeController.getCurrentBrightnessMin(), stateMax)); final float maxBrightness = Math.min( @@ -2055,6 +2060,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call changed |= mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason, maxReason); + changed |= + mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessReason, + brightnessReason.getReason()); return changed; } } @@ -2683,6 +2691,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call + mCachedBrightnessInfo.hbmTransitionPoint.value); pw.println(" mCachedBrightnessInfo.brightnessMaxReason =" + mCachedBrightnessInfo.brightnessMaxReason.value); + pw.println(" mCachedBrightnessInfo.brightnessReason =" + + mCachedBrightnessInfo.brightnessReason); } pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig); pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig); @@ -3390,6 +3400,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID); public MutableInt brightnessMaxReason = new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE); + public MutableInt brightnessReason = new MutableInt(BrightnessReason.REASON_UNKNOWN); public boolean checkAndSetFloat(MutableFloat mf, float f) { if (mf.value != f) { diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java index b101e5893b97..47226861545f 100644 --- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java @@ -16,6 +16,7 @@ package com.android.server.display; +import android.hardware.display.DisplayTopology; import android.util.DisplayMetrics; import android.view.Display; import android.view.DisplayInfo; @@ -33,7 +34,7 @@ import java.util.function.BooleanSupplier; class DisplayTopologyCoordinator { @GuardedBy("mLock") - private final DisplayTopology mTopology; + private DisplayTopology mTopology; /** * Check if extended displays are enabled. If not, a topology is not needed. @@ -76,6 +77,21 @@ class DisplayTopologyCoordinator { } /** + * @return A deep copy of the topology. + */ + DisplayTopology getTopology() { + synchronized (mLock) { + return mTopology; + } + } + + void setTopology(DisplayTopology topology) { + synchronized (mLock) { + mTopology = topology; + } + } + + /** * Print the object's state and debug information into the given stream. * @param pw The stream to dump information to. */ @@ -108,6 +124,7 @@ class DisplayTopologyCoordinator { && info.displayGroupId == Display.DEFAULT_DISPLAY_GROUP; } + @VisibleForTesting static class Injector { DisplayTopology getTopology() { return new DisplayTopology(); 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 a10094fdfbb8..6e579bf161ee 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 @@ -172,17 +172,18 @@ public class BrightnessClamperController { * Applies clamping * Called in DisplayControllerHandler */ - public DisplayBrightnessState clamp(DisplayManagerInternal.DisplayPowerRequest request, + public DisplayBrightnessState clamp(DisplayBrightnessState displayBrightnessState, + DisplayManagerInternal.DisplayPowerRequest request, float brightnessValue, boolean slowChange, int displayState) { float cappedBrightness = Math.min(brightnessValue, mBrightnessCap); - DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder(); + DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from( + displayBrightnessState); builder.setIsSlowChange(slowChange); builder.setBrightness(cappedBrightness); builder.setMaxBrightness(mBrightnessCap); builder.setCustomAnimationRate(mCustomAnimationRate); builder.setBrightnessMaxReason(getBrightnessMaxReason()); - if (mClamperType != null) { builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED); if (!mClamperApplied) { diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 88562ab9ba2d..8423e1911764 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -2077,8 +2077,8 @@ public class DisplayModeDirector { mDeviceConfigDisplaySettings.startListening(); mInjector.registerDisplayListener(this, mHandler, - DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS); + DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, + DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS); } private void setLoggingEnabled(boolean loggingEnabled) { @@ -2878,8 +2878,8 @@ public class DisplayModeDirector { } mDisplayManagerInternal = mInjector.getDisplayManagerInternal(); mInjector.registerDisplayListener(this, mHandler, - DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); + DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, + DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS); } /** @@ -3108,6 +3108,9 @@ public class DisplayModeDirector { void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener, Handler handler, long flags); + void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener, + Handler handler, long flags, long privateFlags); + Display getDisplay(int displayId); Display[] getDisplays(); @@ -3175,6 +3178,12 @@ public class DisplayModeDirector { } @Override + public void registerDisplayListener(DisplayManager.DisplayListener listener, + Handler handler, long flags, long privateFlags) { + getDisplayManager().registerDisplayListener(listener, handler, flags, privateFlags); + } + + @Override public Display getDisplay(int displayId) { return getDisplayManager().getDisplay(displayId); } diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 794eb8754820..0c04be10d06d 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.BIND_DREAM_SERVICE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.service.dreams.Flags.cleanupDreamSettingsOnUninstall; import static android.service.dreams.Flags.dreamHandlesBeingObscured; import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID; @@ -353,28 +354,32 @@ public final class DreamManagerService extends SystemService { @Override public void onUserStarting(@NonNull TargetUser user) { super.onUserStarting(user); - mHandler.post(() -> { - final int userId = user.getUserIdentifier(); - if (!mPackageMonitors.contains(userId)) { - final PackageMonitor monitor = new PerUserPackageMonitor(); - monitor.register(mContext, UserHandle.of(userId), mHandler); - mPackageMonitors.put(userId, monitor); - } else { - Slog.w(TAG, "Package monitor already registered for " + userId); - } - }); + if (cleanupDreamSettingsOnUninstall()) { + mHandler.post(() -> { + final int userId = user.getUserIdentifier(); + if (!mPackageMonitors.contains(userId)) { + final PackageMonitor monitor = new PerUserPackageMonitor(); + monitor.register(mContext, UserHandle.of(userId), mHandler); + mPackageMonitors.put(userId, monitor); + } else { + Slog.w(TAG, "Package monitor already registered for " + userId); + } + }); + } } @Override public void onUserStopping(@NonNull TargetUser user) { super.onUserStopping(user); - mHandler.post(() -> { - final PackageMonitor monitor = mPackageMonitors.removeReturnOld( - user.getUserIdentifier()); - if (monitor != null) { - monitor.unregister(); - } - }); + if (cleanupDreamSettingsOnUninstall()) { + mHandler.post(() -> { + final PackageMonitor monitor = mPackageMonitors.removeReturnOld( + user.getUserIdentifier()); + if (monitor != null) { + monitor.unregister(); + } + }); + } } private void dumpInternal(PrintWriter pw) { @@ -715,15 +720,23 @@ public final class DreamManagerService extends SystemService { userId)); if (componentNames != null) { // Filter out any components in the removed package. - final ComponentName[] filteredComponents = Arrays.stream(componentNames).filter( - (componentName -> !TextUtils.equals(componentName.getPackageName(), - packageName))).toArray(ComponentName[]::new); + final ComponentName[] filteredComponents = + Arrays.stream(componentNames) + .filter((componentName -> !isSamePackage(packageName, componentName))) + .toArray(ComponentName[]::new); if (filteredComponents.length != componentNames.length) { setDreamComponentsForUser(userId, filteredComponents); } } } + private static boolean isSamePackage(String packageName, ComponentName componentName) { + if (packageName == null || componentName == null) { + return false; + } + return TextUtils.equals(componentName.getPackageName(), packageName); + } + private void setDreamComponentsForUser(int userId, ComponentName[] componentNames) { Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.SCREENSAVER_COMPONENTS, @@ -884,7 +897,10 @@ public final class DreamManagerService extends SystemService { } StringBuilder names = new StringBuilder(); for (ComponentName componentName : componentNames) { - if (names.length() > 0) { + if (componentName == null) { + continue; + } + if (!names.isEmpty()) { names.append(','); } names.append(componentName.flattenToString()); diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig index 69ba785b3b4f..eea5c982c537 100644 --- a/services/core/java/com/android/server/flags/services.aconfig +++ b/services/core/java/com/android/server/flags/services.aconfig @@ -67,3 +67,14 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "backstage_power" + name: "rate_limit_battery_changed_broadcast" + description: "Optimize the delivery of the battery changed broadcast by rate limiting the frequency of the updates" + bug: "362337621" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index f2e2f653f929..5b4c0337862b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -1001,7 +1001,7 @@ final class HdmiCecController { try { // Create an AIDL callback that can callback onHotplugEvent mHdmiConnection.setCallback(new HdmiConnectionCallbackAidl(callback)); - } catch (RemoteException e) { + } catch (RemoteException | NullPointerException e) { HdmiLogger.error("Couldn't initialise tv.hdmi callback : ", e); } } @@ -1134,7 +1134,7 @@ final class HdmiCecController { i++; } return hdmiPortInfo; - } catch (RemoteException e) { + } catch (RemoteException | NullPointerException e) { HdmiLogger.error("Failed to get port information : ", e); return null; } @@ -1144,7 +1144,7 @@ final class HdmiCecController { public boolean nativeIsConnected(int port) { try { return mHdmiConnection.isConnected(port); - } catch (RemoteException e) { + } catch (RemoteException | NullPointerException e) { HdmiLogger.error("Failed to get connection info : ", e); return false; } @@ -1158,7 +1158,7 @@ final class HdmiCecController { HdmiLogger.error( "Could not set HPD signal type for portId " + portId + " to " + signal + ". Error: ", sse.errorCode); - } catch (RemoteException e) { + } catch (RemoteException | NullPointerException e) { HdmiLogger.error( "Could not set HPD signal type for portId " + portId + " to " + signal + ". Exception: ", e); @@ -1169,7 +1169,7 @@ final class HdmiCecController { public int nativeGetHpdSignalType(int portId) { try { return mHdmiConnection.getHpdSignal(portId); - } catch (RemoteException e) { + } catch (RemoteException | NullPointerException e) { HdmiLogger.error( "Could not get HPD signal type for portId " + portId + ". Exception: ", e); return Constants.HDMI_HPD_TYPE_PHYSICAL; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index bf415a344f4c..7505c710f483 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -646,9 +646,9 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { int address = message.getSource(); int type = message.getParams()[2]; - if (!ActiveSource.of(address, path).equals(getActiveSource())) { - HdmiLogger.debug("Check if a new device is connected to the active path"); - handleNewDeviceAtTheTailOfActivePath(path); + if (getActiveSource().logicalAddress != address && getActivePath() == path) { + HdmiLogger.debug("New logical address detected on the current active path."); + startRoutingControl(path, path, null); } startNewDeviceAction(ActiveSource.of(address, path), type); return Constants.HANDLED; diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 132d6fa377eb..0c5069f81774 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -771,6 +771,14 @@ public class HdmiControlService extends SystemService { Slog.i(TAG, "Device does not support eARC."); } mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController); + if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) { + Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy " + + " mode."); + getHdmiCecConfig().setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, + HDMI_CEC_CONTROL_ENABLED); + setWasCecDisabledOnStandbyByLowEnergyMode(false); + setCecEnabled(HDMI_CEC_CONTROL_ENABLED); + } if (isCecControlEnabled()) { initializeCec(INITIATED_BY_BOOT_UP); } else { diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java index 8cb51ce35a89..e545dd507f28 100644 --- a/services/core/java/com/android/server/input/InputGestureManager.java +++ b/services/core/java/com/android/server/input/InputGestureManager.java @@ -323,26 +323,50 @@ final class InputGestureManager { return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST; } customGestures.remove(data.getTrigger()); - if (customGestures.size() == 0) { + if (customGestures.isEmpty()) { mCustomInputGestures.remove(userId); } return InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS; } } - public void removeAllCustomInputGestures(int userId) { + public void removeAllCustomInputGestures(int userId, @Nullable InputGestureData.Filter filter) { synchronized (mGestureLock) { - mCustomInputGestures.remove(userId); + Map<InputGestureData.Trigger, InputGestureData> customGestures = + mCustomInputGestures.get(userId); + if (customGestures == null) { + return; + } + if (filter == null) { + mCustomInputGestures.remove(userId); + return; + } + customGestures.entrySet().removeIf(entry -> filter.matches(entry.getValue())); + if (customGestures.isEmpty()) { + mCustomInputGestures.remove(userId); + } } } @NonNull - public List<InputGestureData> getCustomInputGestures(int userId) { + public List<InputGestureData> getCustomInputGestures(int userId, + @Nullable InputGestureData.Filter filter) { synchronized (mGestureLock) { if (!mCustomInputGestures.contains(userId)) { return List.of(); } - return new ArrayList<>(mCustomInputGestures.get(userId).values()); + Map<InputGestureData.Trigger, InputGestureData> customGestures = + mCustomInputGestures.get(userId); + if (filter == null) { + return new ArrayList<>(customGestures.values()); + } + List<InputGestureData> result = new ArrayList<>(); + for (InputGestureData customGesture : customGestures.values()) { + if (filter.matches(customGesture)) { + result.add(customGesture); + } + } + return result; } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index e0f3a9bd427a..f4dd71706761 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -3017,15 +3017,16 @@ public class InputManagerService extends IInputManager.Stub @Override @PermissionManuallyEnforced - public void removeAllCustomInputGestures(@UserIdInt int userId) { + public void removeAllCustomInputGestures(@UserIdInt int userId, int tag) { enforceManageKeyGesturePermission(); - mKeyGestureController.removeAllCustomInputGestures(userId); + mKeyGestureController.removeAllCustomInputGestures(userId, InputGestureData.Filter.of(tag)); } @Override - public AidlInputGestureData[] getCustomInputGestures(@UserIdInt int userId) { - return mKeyGestureController.getCustomInputGestures(userId); + public AidlInputGestureData[] getCustomInputGestures(@UserIdInt int userId, int tag) { + return mKeyGestureController.getCustomInputGestures(userId, + InputGestureData.Filter.of(tag)); } @Override diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java index fc106404049c..0124e25f0655 100644 --- a/services/core/java/com/android/server/input/KeyGestureController.java +++ b/services/core/java/com/android/server/input/KeyGestureController.java @@ -20,6 +20,7 @@ import static android.content.pm.PackageManager.FEATURE_LEANBACK; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE; +import static com.android.hardware.input.Flags.enableNew25q2Keycodes; import static com.android.hardware.input.Flags.useKeyGestureEventHandler; import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures; @@ -750,6 +751,28 @@ final class KeyGestureController { } } break; + case KeyEvent.KEYCODE_LOCK: + if (enableNew25q2Keycodes()) { + if (firstDown) { + handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_LOCK}, + /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, + /* flags = */0, /* appLaunchData = */null); + } + } + return true; + case KeyEvent.KEYCODE_FULLSCREEN: + if (enableNew25q2Keycodes()) { + if (firstDown) { + handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_FULLSCREEN}, + /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, + /* flags = */0, /* appLaunchData = */null); + } + } + return true; case KeyEvent.KEYCODE_ASSIST: Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing"); return true; @@ -764,6 +787,9 @@ final class KeyGestureController { Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in" + " interceptKeyBeforeQueueing"); return true; + case KeyEvent.KEYCODE_DO_NOT_DISTURB: + // TODO(b/365920375): Implement 25Q2 keycode implementation in system + return true; } // Handle custom shortcuts @@ -1021,13 +1047,16 @@ final class KeyGestureController { } @BinderThread - public void removeAllCustomInputGestures(@UserIdInt int userId) { - mInputGestureManager.removeAllCustomInputGestures(userId); + public void removeAllCustomInputGestures(@UserIdInt int userId, + @Nullable InputGestureData.Filter filter) { + mInputGestureManager.removeAllCustomInputGestures(userId, filter); } @BinderThread - public AidlInputGestureData[] getCustomInputGestures(@UserIdInt int userId) { - List<InputGestureData> customGestures = mInputGestureManager.getCustomInputGestures(userId); + public AidlInputGestureData[] getCustomInputGestures(@UserIdInt int userId, + @Nullable InputGestureData.Filter filter) { + List<InputGestureData> customGestures = mInputGestureManager.getCustomInputGestures(userId, + filter); AidlInputGestureData[] result = new AidlInputGestureData[customGestures.size()]; for (int i = 0; i < customGestures.size(); i++) { result[i] = customGestures.get(i).getAidlData(); 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 acc8f6634f5c..f611c57dab03 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -35,6 +35,7 @@ import android.hardware.contexthub.MessageDeliveryStatus; import android.hardware.location.ContextHubInfo; import android.hardware.location.ContextHubMessage; import android.hardware.location.ContextHubTransaction; +import android.hardware.location.HubInfo; import android.hardware.location.IContextHubCallback; import android.hardware.location.IContextHubClient; import android.hardware.location.IContextHubClientCallback; @@ -57,6 +58,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Pair; import android.util.proto.ProtoOutputStream; @@ -134,6 +136,9 @@ public class ContextHubService extends IContextHubService.Stub { private Map<Integer, ContextHubInfo> mContextHubIdToInfoMap; private List<String> mSupportedContextHubPerms; private List<ContextHubInfo> mContextHubInfoList; + + @Nullable private final HubInfoRegistry mHubInfoRegistry; + private final RemoteCallbackList<IContextHubCallback> mCallbacksList = new RemoteCallbackList<>(); @@ -309,10 +314,21 @@ public class ContextHubService extends IContextHubService.Stub { mContext = context; long startTimeNs = SystemClock.elapsedRealtimeNanos(); mContextHubWrapper = contextHubWrapper; + if (!initContextHubServiceState(startTimeNs)) { Log.e(TAG, "Failed to initialize the Context Hub Service"); + mHubInfoRegistry = null; return; } + + if (Flags.offloadApi()) { + mHubInfoRegistry = new HubInfoRegistry(mContextHubWrapper); + Log.i(TAG, "Enabling generic offload API"); + } else { + mHubInfoRegistry = null; + Log.i(TAG, "Disabling generic offload API"); + } + initDefaultClientMap(); initLocationSettingNotifications(); @@ -427,7 +443,7 @@ public class ContextHubService extends IContextHubService.Stub { Pair<List<ContextHubInfo>, List<String>> hubInfo; try { - hubInfo = mContextHubWrapper.getHubs(); + hubInfo = mContextHubWrapper.getContextHubs(); } catch (RemoteException e) { Log.e(TAG, "RemoteException while getting Context Hub info", e); hubInfo = new Pair<>(Collections.emptyList(), Collections.emptyList()); @@ -713,6 +729,16 @@ public class ContextHubService extends IContextHubService.Stub { return mContextHubInfoList; } + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @Override + public List<HubInfo> getHubs() throws RemoteException { + super.getHubs_enforcePermission(); + if (mHubInfoRegistry == null) { + return Collections.emptyList(); + } + return mHubInfoRegistry.getHubs(); + } + /** * Creates an internal load transaction callback to be used for old API clients * @@ -1417,6 +1443,8 @@ public class ContextHubService extends IContextHubService.Stub { } } + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + pw = ipw; pw.println("Dumping ContextHub Service"); pw.println(""); @@ -1428,6 +1456,11 @@ public class ContextHubService extends IContextHubService.Stub { pw.println("Supported permissions: " + Arrays.toString(mSupportedContextHubPerms.toArray())); pw.println(""); + + if (mHubInfoRegistry != null) { + mHubInfoRegistry.dump(ipw); + } + pw.println("=================== NANOAPPS ===================="); // Dump nanoAppHash mNanoAppStateManager.foreachNanoAppInstanceInfo(pw::println); diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java new file mode 100644 index 000000000000..68de9dbda2e1 --- /dev/null +++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java @@ -0,0 +1,60 @@ +/* + * 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.location.contexthub; + +import android.hardware.location.HubInfo; +import android.os.RemoteException; +import android.util.IndentingPrintWriter; +import android.util.Log; + +import java.util.Collections; +import java.util.List; + +class HubInfoRegistry { + private static final String TAG = "HubInfoRegistry"; + + private final IContextHubWrapper mContextHubWrapper; + + private final List<HubInfo> mHubsInfo; + + HubInfoRegistry(IContextHubWrapper contextHubWrapper) { + List<HubInfo> hubInfos; + mContextHubWrapper = contextHubWrapper; + try { + hubInfos = mContextHubWrapper.getHubs(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while getting Hub info", e); + hubInfos = Collections.emptyList(); + } + mHubsInfo = hubInfos; + } + + /** Retrieve the list of hubs available. */ + List<HubInfo> getHubs() { + return mHubsInfo; + } + + void dump(IndentingPrintWriter ipw) { + ipw.println(TAG); + + ipw.increaseIndent(); + for (HubInfo hubInfo : mHubsInfo) { + ipw.println(hubInfo); + } + ipw.decreaseIndent(); + } +} 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 5e9277ac0faf..6656a6fe9eb4 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -30,9 +30,11 @@ import android.hardware.contexthub.V1_2.HubAppInfo; import android.hardware.contexthub.V1_2.IContexthubCallback; import android.hardware.location.ContextHubInfo; import android.hardware.location.ContextHubTransaction; +import android.hardware.location.HubInfo; import android.hardware.location.NanoAppBinary; import android.hardware.location.NanoAppMessage; import android.hardware.location.NanoAppState; +import android.hardware.location.VendorHubInfo; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -52,13 +54,14 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; /** * @hide */ public abstract class IContextHubWrapper { + private static final boolean DEBUG = false; private static final String TAG = "IContextHubWrapper"; /** @@ -217,10 +220,14 @@ public abstract class IContextHubWrapper { return proxy == null ? null : new ContextHubWrapperAidl(proxy); } - /** - * Calls the appropriate getHubs function depending on the HAL version. - */ - public abstract Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException; + /** Calls the appropriate getHubs function depending on the HAL version. */ + public abstract Pair<List<ContextHubInfo>, List<String>> getContextHubs() + throws RemoteException; + + /** Calls the appropriate getHubs function depending on the HAL version. */ + public List<HubInfo> getHubs() throws RemoteException { + return Collections.emptyList(); + } /** * @return True if this version of the Contexthub HAL supports Location setting notifications. @@ -556,7 +563,7 @@ public abstract class IContextHubWrapper { mIsTestModeEnabled.set(false); } - public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException { + public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException { android.hardware.contexthub.IContextHub hub = getHub(); if (hub == null) { return new Pair<List<ContextHubInfo>, List<String>>(new ArrayList<ContextHubInfo>(), @@ -574,6 +581,47 @@ public abstract class IContextHubWrapper { return new Pair(hubInfoList, new ArrayList<String>(supportedPermissions)); } + public List<HubInfo> getHubs() throws RemoteException { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return Collections.emptyList(); + } + + List<HubInfo> retVal = new ArrayList<>(); + final List<android.hardware.contexthub.HubInfo> halHubs = hub.getHubs(); + + for (android.hardware.contexthub.HubInfo halHub : halHubs) { + /* HAL -> API Type conversion */ + final HubInfo hubInfo; + switch (halHub.hubDetails.getTag()) { + case android.hardware.contexthub.HubInfo.HubDetails.contextHubInfo: + ContextHubInfo contextHubInfo = + new ContextHubInfo(halHub.hubDetails.getContextHubInfo()); + hubInfo = new HubInfo(halHub.hubId, contextHubInfo); + break; + case android.hardware.contexthub.HubInfo.HubDetails.vendorHubInfo: + VendorHubInfo vendorHubInfo = + new VendorHubInfo(halHub.hubDetails.getVendorHubInfo()); + hubInfo = new HubInfo(halHub.hubId, vendorHubInfo); + break; + default: + Log.w(TAG, "getHubs: invalid hub: " + halHub); + // Invalid + continue; + } + + if (DEBUG) { + Log.i(TAG, "getHubs: hubInfo=" + hubInfo); + } + retVal.add(hubInfo); + } + + if (DEBUG) { + Log.i(TAG, "getHubs: total count=" + retVal.size()); + } + return retVal; + } + public boolean supportsLocationSettingNotifications() { return true; } @@ -1061,7 +1109,7 @@ public abstract class IContextHubWrapper { mHub = hub; } - public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException { + public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException { ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>(); for (ContextHub hub : mHub.getHubs()) { hubInfoList.add(new ContextHubInfo(hub)); @@ -1106,7 +1154,7 @@ public abstract class IContextHubWrapper { mHub = hub; } - public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException { + public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException { ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>(); for (ContextHub hub : mHub.getHubs()) { hubInfoList.add(new ContextHubInfo(hub)); @@ -1170,7 +1218,7 @@ public abstract class IContextHubWrapper { mHubInfo = new Pair(hubInfoList, supportedPermissions); } - public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException { + public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException { mHub.getHubs_1_2(this); return mHubInfo; } 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 a45ea1d8369d..21ae1820f6f7 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -59,15 +59,15 @@ public class MediaQualityService extends SystemService { return pp; } @Override - public void updatePictureProfile(long id, PictureProfile pp) { + public void updatePictureProfile(String id, PictureProfile pp) { // TODO: implement } @Override - public void removePictureProfile(long id) { + public void removePictureProfile(String id) { // TODO: implement } @Override - public PictureProfile getPictureProfileById(long id) { + public PictureProfile getPictureProfile(int type, String name) { return null; } @Override @@ -79,7 +79,7 @@ public class MediaQualityService extends SystemService { return new ArrayList<>(); } @Override - public List<PictureProfile> getAllPictureProfiles() { + public List<String> getPictureProfilePackageNames() { return new ArrayList<>(); } @@ -89,15 +89,15 @@ public class MediaQualityService extends SystemService { return pp; } @Override - public void updateSoundProfile(long id, SoundProfile pp) { + public void updateSoundProfile(String id, SoundProfile pp) { // TODO: implement } @Override - public void removeSoundProfile(long id) { + public void removeSoundProfile(String id) { // TODO: implement } @Override - public SoundProfile getSoundProfileById(long id) { + public SoundProfile getSoundProfileById(String id) { return null; } @Override @@ -109,7 +109,7 @@ public class MediaQualityService extends SystemService { return new ArrayList<>(); } @Override - public List<SoundProfile> getAllSoundProfiles() { + public List<String> getSoundProfilePackageNames() { return new ArrayList<>(); } @@ -138,6 +138,14 @@ public class MediaQualityService extends SystemService { return new ArrayList<>(); } + @Override + public List<String> getPictureProfileAllowList() { + return new ArrayList<>(); + } + + @Override + public void setPictureProfileAllowList(List<String> packages) { + } @Override public boolean isSupported() { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 2a3be1e119bf..7de2815eba6b 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -513,12 +513,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private boolean mLoadedRestrictBackground; /** - * Whether or not network for apps in proc-states greater than - * {@link NetworkPolicyManager#BACKGROUND_THRESHOLD_STATE} is always blocked. - */ - private boolean mBackgroundNetworkRestricted; - - /** * Whether or not metered firewall chains should be used for uid policy controlling access to * metered networks. */ @@ -1117,14 +1111,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { writePolicyAL(); } - // The flag is boot-stable. - mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove(); - if (mBackgroundNetworkRestricted) { - // Firewall rules and UidBlockedState will get updated in - // updateRulesForGlobalChangeAL below. - enableFirewallChainUL(FIREWALL_CHAIN_BACKGROUND, true); - } - + enableFirewallChainUL(FIREWALL_CHAIN_BACKGROUND, true); setRestrictBackgroundUL(mLoadedRestrictBackground, "init_service"); updateRulesForGlobalChangeAL(false); updateNotificationsNL(); @@ -1135,11 +1122,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int changes = ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_CAPABILITY; - - final int cutpoint = mBackgroundNetworkRestricted ? PROCESS_STATE_UNKNOWN - : NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE; mActivityManagerInternal.registerNetworkPolicyUidObserver(mUidObserver, changes, - cutpoint, "android"); + PROCESS_STATE_UNKNOWN, "android"); mNetworkManager.registerObserver(mAlertObserver); } catch (RemoteException e) { // ignored; both services live in system_server @@ -1280,21 +1264,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // different chains may change. return true; } - if (mBackgroundNetworkRestricted) { - if ((previousProcState >= BACKGROUND_THRESHOLD_STATE) + if ((previousProcState >= BACKGROUND_THRESHOLD_STATE) != (newProcState >= BACKGROUND_THRESHOLD_STATE)) { - // Proc-state change crossed BACKGROUND_THRESHOLD_STATE: The network rules will - // need to be re-evaluated for the background chain. - return true; - } - if (mUseDifferentDelaysForBackgroundChain - && newProcState >= BACKGROUND_THRESHOLD_STATE - && getBackgroundTransitioningDelay(newProcState) - < getBackgroundTransitioningDelay(previousProcState)) { - // The old and new proc-state both are in the blocked state but the background - // transition delay is reduced, so we may have to update the rules sooner. - return true; - } + // Proc-state change crossed BACKGROUND_THRESHOLD_STATE: The network rules will + // need to be re-evaluated for the background chain. + return true; + } + if (mUseDifferentDelaysForBackgroundChain + && newProcState >= BACKGROUND_THRESHOLD_STATE + && getBackgroundTransitioningDelay(newProcState) + < getBackgroundTransitioningDelay(previousProcState)) { + // The old and new proc-state both are in the blocked state but the background + // transition delay is reduced, so we may have to update the rules sooner. + return true; } final int networkCapabilities = PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; @@ -1367,9 +1349,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // on background handler thread, and POWER_SAVE_WHITELIST_CHANGED is protected synchronized (mUidRulesFirstLock) { updatePowerSaveAllowlistUL(); - if (mBackgroundNetworkRestricted) { - updateRulesForBackgroundChainUL(); - } + updateRulesForBackgroundChainUL(); updateRulesForRestrictPowerUL(); updateRulesForAppIdleUL(); } @@ -4100,8 +4080,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { fout.println(); fout.println("Flags:"); - fout.println(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE + ": " - + mBackgroundNetworkRestricted); fout.println(Flags.FLAG_USE_METERED_FIREWALL_CHAINS + ": " + mUseMeteredFirewallChains); fout.println(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN + ": " @@ -4251,35 +4229,33 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { fout.decreaseIndent(); } - if (mBackgroundNetworkRestricted) { + fout.println(); + if (mUseDifferentDelaysForBackgroundChain) { + fout.print("Background restrictions short delay: "); + TimeUtils.formatDuration(mBackgroundRestrictionShortDelayMs, fout); fout.println(); - if (mUseDifferentDelaysForBackgroundChain) { - fout.print("Background restrictions short delay: "); - TimeUtils.formatDuration(mBackgroundRestrictionShortDelayMs, fout); - fout.println(); - fout.print("Background restrictions long delay: "); - TimeUtils.formatDuration(mBackgroundRestrictionLongDelayMs, fout); - fout.println(); - } + fout.print("Background restrictions long delay: "); + TimeUtils.formatDuration(mBackgroundRestrictionLongDelayMs, fout); + fout.println(); + } - size = mBackgroundTransitioningUids.size(); - if (size > 0) { - final long nowUptime = SystemClock.uptimeMillis(); - fout.println("Uids transitioning to background:"); - fout.increaseIndent(); - for (int i = 0; i < size; i++) { - fout.print("UID="); - fout.print(mBackgroundTransitioningUids.keyAt(i)); - fout.print(", "); - TimeUtils.formatDuration(mBackgroundTransitioningUids.valueAt(i), - nowUptime, fout); - fout.println(); - } - fout.decreaseIndent(); + size = mBackgroundTransitioningUids.size(); + if (size > 0) { + final long nowUptime = SystemClock.uptimeMillis(); + fout.println("Uids transitioning to background:"); + fout.increaseIndent(); + for (int i = 0; i < size; i++) { + fout.print("UID="); + fout.print(mBackgroundTransitioningUids.keyAt(i)); + fout.print(", "); + TimeUtils.formatDuration(mBackgroundTransitioningUids.valueAt(i), + nowUptime, fout); + fout.println(); } - fout.println(); + fout.decreaseIndent(); } + fout.println(); final SparseBooleanArray knownUids = new SparseBooleanArray(); collectKeys(mUidState, knownUids); @@ -4465,51 +4441,49 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } updatePowerRestrictionRules = true; } - if (mBackgroundNetworkRestricted) { - final boolean wasAllowed = isProcStateAllowedNetworkWhileBackground( - oldUidState); - final boolean isAllowed = isProcStateAllowedNetworkWhileBackground(newUidState); - if (!wasAllowed && isAllowed) { - mBackgroundTransitioningUids.delete(uid); - updateRuleForBackgroundUL(uid); - updatePowerRestrictionRules = true; - } else if (!isAllowed) { - final int transitionIdx = mBackgroundTransitioningUids.indexOfKey(uid); - final long completionTimeMs = SystemClock.uptimeMillis() - + getBackgroundTransitioningDelay(procState); - boolean completionTimeUpdated = false; - if (wasAllowed) { - // Rules need to transition from allowed to blocked after the respective - // delay. - if (transitionIdx < 0) { - // This is just a defensive check in case the upstream code ever - // makes multiple calls for the same process state change. - mBackgroundTransitioningUids.put(uid, completionTimeMs); - completionTimeUpdated = true; - } - } else if (mUseDifferentDelaysForBackgroundChain) { - // wasAllowed was false, but the transition delay may have reduced. - // Currently, this can happen when the uid transitions from - // LAST_ACTIVITY to CACHED_ACTIVITY, for example. - if (transitionIdx >= 0 - && completionTimeMs < mBackgroundTransitioningUids.valueAt( - transitionIdx)) { - mBackgroundTransitioningUids.setValueAt(transitionIdx, - completionTimeMs); - completionTimeUpdated = true; - } + final boolean wasAllowed = isProcStateAllowedNetworkWhileBackground( + oldUidState); + final boolean isAllowed = isProcStateAllowedNetworkWhileBackground(newUidState); + if (!wasAllowed && isAllowed) { + mBackgroundTransitioningUids.delete(uid); + updateRuleForBackgroundUL(uid); + updatePowerRestrictionRules = true; + } else if (!isAllowed) { + final int transitionIdx = mBackgroundTransitioningUids.indexOfKey(uid); + final long completionTimeMs = SystemClock.uptimeMillis() + + getBackgroundTransitioningDelay(procState); + boolean completionTimeUpdated = false; + if (wasAllowed) { + // Rules need to transition from allowed to blocked after the respective + // delay. + if (transitionIdx < 0) { + // This is just a defensive check in case the upstream code ever + // makes multiple calls for the same process state change. + mBackgroundTransitioningUids.put(uid, completionTimeMs); + completionTimeUpdated = true; } - if (completionTimeUpdated - && completionTimeMs < mNextProcessBackgroundUidsTime) { - // Many uids may be in this "transitioning" state at the same time, - // so we always keep one message to process transition completion at - // the earliest time. - mHandler.removeMessages(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS); - mHandler.sendEmptyMessageAtTime( - MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS, completionTimeMs); - mNextProcessBackgroundUidsTime = completionTimeMs; + } else if (mUseDifferentDelaysForBackgroundChain) { + // wasAllowed was false, but the transition delay may have reduced. + // Currently, this can happen when the uid transitions from + // LAST_ACTIVITY to CACHED_ACTIVITY, for example. + if (transitionIdx >= 0 + && completionTimeMs < mBackgroundTransitioningUids.valueAt( + transitionIdx)) { + mBackgroundTransitioningUids.setValueAt(transitionIdx, + completionTimeMs); + completionTimeUpdated = true; } } + if (completionTimeUpdated + && completionTimeMs < mNextProcessBackgroundUidsTime) { + // Many uids may be in this "transitioning" state at the same time, + // so we always keep one message to process transition completion at + // the earliest time. + mHandler.removeMessages(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS); + mHandler.sendEmptyMessageAtTime( + MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS, completionTimeMs); + mNextProcessBackgroundUidsTime = completionTimeMs; + } } if (mLowPowerStandbyActive) { boolean allowedInLpsChanged = @@ -4545,12 +4519,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (mRestrictPower) { updateRuleForRestrictPowerUL(uid); } - if (mBackgroundNetworkRestricted) { - // Uid is no longer running, there is no point in any grace period of network - // access during transitions to lower importance proc-states. - mBackgroundTransitioningUids.delete(uid); - updateRuleForBackgroundUL(uid); - } + // Uid is no longer running, there is no point in any grace period of network + // access during transitions to lower importance proc-states. + mBackgroundTransitioningUids.delete(uid); + updateRuleForBackgroundUL(uid); updateRulesForPowerRestrictionsUL(uid); if (mLowPowerStandbyActive) { updateRuleForLowPowerStandbyUL(uid); @@ -5021,9 +4993,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { "updateRulesForGlobalChangeAL: " + (restrictedNetworksChanged ? "R" : "-")); } try { - if (mBackgroundNetworkRestricted) { - updateRulesForBackgroundChainUL(); - } + updateRulesForBackgroundChainUL(); updateRulesForAppIdleUL(); updateRulesForRestrictPowerUL(); updateRulesForRestrictBackgroundUL(); @@ -5183,9 +5153,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN); updateRuleForDeviceIdleUL(uid); updateRuleForRestrictPowerUL(uid); - if (mBackgroundNetworkRestricted) { - updateRuleForBackgroundUL(uid); - } + updateRuleForBackgroundUL(uid); // Update internal rules. updateRulesForPowerRestrictionsUL(uid); } @@ -5358,9 +5326,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { updateRuleForDeviceIdleUL(uid); updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN); updateRuleForRestrictPowerUL(uid); - if (mBackgroundNetworkRestricted) { - updateRuleForBackgroundUL(uid); - } + updateRuleForBackgroundUL(uid); // If the uid has the necessary permissions, then it should be added to the restricted mode // firewall allowlist. @@ -5611,7 +5577,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { newBlockedReasons |= (mLowPowerStandbyActive ? BLOCKED_REASON_LOW_POWER_STANDBY : 0); newBlockedReasons |= (isUidIdle ? BLOCKED_REASON_APP_STANDBY : 0); newBlockedReasons |= (uidBlockedState.blockedReasons & BLOCKED_REASON_RESTRICTED_MODE); - newBlockedReasons |= mBackgroundNetworkRestricted ? BLOCKED_REASON_APP_BACKGROUND : 0; + newBlockedReasons |= BLOCKED_REASON_APP_BACKGROUND; newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0); newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0); @@ -5624,8 +5590,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS); newAllowedReasons |= (isAllowlistedFromLowPowerStandbyUL(uid)) ? ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST : 0; - newAllowedReasons |= (mBackgroundNetworkRestricted - && isUidExemptFromBackgroundRestrictions(uid)) + newAllowedReasons |= isUidExemptFromBackgroundRestrictions(uid) ? ALLOWED_REASON_NOT_IN_BACKGROUND : 0; uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig index 7f04e665567e..3c0ff6115fcd 100644 --- a/services/core/java/com/android/server/net/flags.aconfig +++ b/services/core/java/com/android/server/net/flags.aconfig @@ -2,13 +2,6 @@ package: "com.android.server.net" container: "system" flag { - name: "network_blocked_for_top_sleeping_and_above" - namespace: "backstage_power" - description: "Block network access for apps in a low importance background state" - bug: "304347838" -} - -flag { name: "use_metered_firewall_chains" namespace: "backstage_power" description: "Use metered firewall chains to control access to metered networks" diff --git a/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java new file mode 100644 index 000000000000..8ec716077f46 --- /dev/null +++ b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.os.instrumentation; + +import static android.Manifest.permission.DYNAMIC_INSTRUMENTATION; +import static android.content.Context.DYNAMIC_INSTRUMENTATION_SERVICE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.PermissionManuallyEnforced; +import android.content.Context; +import android.os.instrumentation.ExecutableMethodFileOffsets; +import android.os.instrumentation.IDynamicInstrumentationManager; +import android.os.instrumentation.MethodDescriptor; +import android.os.instrumentation.TargetProcess; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.SystemService; + +import dalvik.system.VMDebug; + +import java.lang.reflect.Method; + +/** + * System private implementation of the {@link IDynamicInstrumentationManager interface}. + */ +public class DynamicInstrumentationManagerService extends SystemService { + public DynamicInstrumentationManagerService(@NonNull Context context) { + super(context); + } + + @Override + public void onStart() { + 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) { + if (!com.android.art.flags.Flags.executableMethodFileOffsets()) { + throw new UnsupportedOperationException(); + } + getContext().enforceCallingOrSelfPermission( + DYNAMIC_INSTRUMENTATION, "Caller must have DYNAMIC_INSTRUMENTATION permission"); + + if (targetProcess.processName == null + || !targetProcess.processName.equals("system_server")) { + throw new UnsupportedOperationException( + "system_server is the only supported target process"); + } + + Method method = 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); + } + 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/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java index af2bb17fd0e6..d538bb876b64 100644 --- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java +++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java @@ -265,6 +265,7 @@ public class BackgroundInstallControlService extends SystemService { @Override public void handleMessage(Message msg) { + Slog.d(TAG, "Package event received: " + msg.what); switch (msg.what) { case MSG_USAGE_EVENT_RECEIVED: mService.handleUsageEvent( @@ -326,6 +327,8 @@ public class BackgroundInstallControlService extends SystemService { return; } + Slog.d(TAG, "handlePackageAdd: adding " + packageName + " from " + + userId + " and notifying callbacks"); initBackgroundInstalledPackages(); mBackgroundInstalledPackages.add(userId, packageName); mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_INSTALL); @@ -364,7 +367,11 @@ public class BackgroundInstallControlService extends SystemService { // ADB sets installerPackageName to null, this creates a loophole to bypass BIC which will be // addressed with b/265203007 private boolean installedByAdb(String initiatingPackageName) { - return PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName); + if(PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName)) { + Slog.d(TAG, "handlePackageAdd: is installed by ADB, skipping"); + return true; + } + return false; } private boolean wasForegroundInstallation( @@ -407,6 +414,7 @@ public class BackgroundInstallControlService extends SystemService { if (mBackgroundInstalledPackages.contains(userId, packageName)) { mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL); } + Slog.d(TAG, "handlePackageRemove: removing " + packageName + " from " + userId); mBackgroundInstalledPackages.remove(userId, packageName); writeBackgroundInstalledPackagesToDisk(); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 5fc3e332b95c..05bc69a9f1f0 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1015,7 +1015,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { permission, attributionSource, message, forDataDelivery, startDataDelivery, fromDatasource, attributedOp); // Finish any started op if some step in the attribution chain failed. - if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) { + if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED + && result != PermissionChecker.PERMISSION_SOFT_DENIED) { if (attributedOp == AppOpsManager.OP_NONE) { finishDataDelivery(AppOpsManager.permissionToOpCode(permission), attributionSource.asState(), fromDatasource); @@ -1244,6 +1245,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE; AttributionSource current = attributionSource; AttributionSource next = null; + AttributionSource prev = null; // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and // every attributionSource in the chain is registered with the system. final boolean isChainStartTrusted = !hasChain || checkPermission(context, @@ -1310,6 +1312,22 @@ public class PermissionManagerService extends IPermissionManager.Stub { selfAccess, singleReceiverFromDatasource, attributedOp, proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); + if (startDataDelivery && opMode != AppOpsManager.MODE_ALLOWED) { + // Current failed the perm check, so if we are part-way through an attr chain, + // we need to clean up the already started proxy op higher up the chain. Note, + // proxy ops are verified two by two, which means we have to clear the 2nd next + // from the previous iteration (since it is actually curr.next which failed + // to pass the perm check). + if (prev != null) { + final var cutAttrSourceState = prev.asState(); + if (cutAttrSourceState.next.length > 0) { + cutAttrSourceState.next[0].next = new AttributionSourceState[0]; + } + finishDataDelivery(context, attributedOp, + cutAttrSourceState, fromDatasource); + } + } + switch (opMode) { case AppOpsManager.MODE_ERRORED: { if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) { @@ -1335,6 +1353,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { return PermissionChecker.PERMISSION_GRANTED; } + // an attribution we have already possibly started an op for + prev = current; current = next; } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 19406b46f5c0..845798f697bf 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -83,7 +83,7 @@ import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED; import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled; -import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable; +import static com.android.hardware.input.Flags.enableNew25q2Keycodes; import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures; import static com.android.hardware.input.Flags.keyboardA11yShortcutControl; import static com.android.hardware.input.Flags.modifierShortcutDump; @@ -3993,10 +3993,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { return true; } case KeyEvent.KEYCODE_SCREENSHOT: - if (emojiAndScreenshotKeycodesAvailable() && down && repeatCount == 0) { + if (firstDown) { interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/); } return true; + case KeyEvent.KEYCODE_DO_NOT_DISTURB: + case KeyEvent.KEYCODE_LOCK: + case KeyEvent.KEYCODE_FULLSCREEN: + return true; } if (isValidGlobalKey(keyCode) && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) { @@ -5666,9 +5670,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_MACRO_4: result &= ~ACTION_PASS_TO_USER; break; - case KeyEvent.KEYCODE_EMOJI_PICKER: - if (!emojiAndScreenshotKeycodesAvailable()) { - // Don't allow EMOJI_PICKER key to be dispatched until flag is released. + case KeyEvent.KEYCODE_DICTATE: + case KeyEvent.KEYCODE_NEW: + case KeyEvent.KEYCODE_CLOSE: + case KeyEvent.KEYCODE_PRINT: + case KeyEvent.KEYCODE_F13: + case KeyEvent.KEYCODE_F14: + case KeyEvent.KEYCODE_F15: + case KeyEvent.KEYCODE_F16: + case KeyEvent.KEYCODE_F17: + case KeyEvent.KEYCODE_F18: + case KeyEvent.KEYCODE_F19: + case KeyEvent.KEYCODE_F20: + case KeyEvent.KEYCODE_F21: + case KeyEvent.KEYCODE_F22: + case KeyEvent.KEYCODE_F23: + case KeyEvent.KEYCODE_F24: + if (!enableNew25q2Keycodes()) { result &= ~ACTION_PASS_TO_USER; } break; diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 677a2dea665b..028ac57fc5a3 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -303,6 +303,8 @@ public class BatteryStatsImpl extends BatteryStats { private final GnssPowerStatsCollector mGnssPowerStatsCollector; private final CustomEnergyConsumerPowerStatsCollector mCustomEnergyConsumerPowerStatsCollector; private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray(); + private boolean mMoveWscLoggingToNotifierEnabled = false; + private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever = new ScreenPowerStatsCollector.ScreenUsageTimeRetriever() { @@ -5155,7 +5157,7 @@ public class BatteryStatsImpl extends BatteryStats { Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs); uidStats.noteStartWakeLocked(pid, name, type, elapsedRealtimeMs); - if (!mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled()) { + if (!mMoveWscLoggingToNotifierEnabled) { mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name, uidStats.mProcessState, true /* acquired */, getPowerManagerWakeLockLevel(type)); @@ -5206,7 +5208,7 @@ public class BatteryStatsImpl extends BatteryStats { Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs); uidStats.noteStopWakeLocked(pid, name, type, elapsedRealtimeMs); - if (!mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled()) { + if (!mMoveWscLoggingToNotifierEnabled) { mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name, uidStats.mProcessState, false/* acquired */, getPowerManagerWakeLockLevel(type)); @@ -15975,6 +15977,15 @@ public class BatteryStatsImpl extends BatteryStats { } } + /** + * Controls where the logging of the WakelockStateChanged atom occurs: + * true = Notifier, false = BatteryStatsImpl. + */ + public void setMoveWscLoggingToNotifierEnabled(boolean enabled) { + synchronized (this) { + mMoveWscLoggingToNotifierEnabled = enabled; + } + } @GuardedBy("this") public void systemServicesReady(Context context) { mConstants.startObserving(context.getContentResolver()); diff --git a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java b/services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java index 9398c7a854d4..b129fdc1b6e3 100644 --- a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java +++ b/services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.adaptiveauth; +package com.android.server.security.adaptiveauthentication; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; @@ -55,8 +55,8 @@ import java.util.Objects; /** * @hide */ -public class AdaptiveAuthService extends SystemService { - private static final String TAG = "AdaptiveAuthService"; +public class AdaptiveAuthenticationService extends SystemService { + private static final String TAG = "AdaptiveAuthenticationService"; private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG); @VisibleForTesting @@ -78,12 +78,12 @@ public class AdaptiveAuthService extends SystemService { final SparseIntArray mFailedAttemptsForUser = new SparseIntArray(); private final SparseLongArray mLastLockedTimestamp = new SparseLongArray(); - public AdaptiveAuthService(Context context) { + public AdaptiveAuthenticationService(Context context) { this(context, new LockPatternUtils(context)); } @VisibleForTesting - public AdaptiveAuthService(Context context, LockPatternUtils lockPatternUtils) { + public AdaptiveAuthenticationService(Context context, LockPatternUtils lockPatternUtils) { super(context); mLockPatternUtils = lockPatternUtils; mLockSettings = Objects.requireNonNull( diff --git a/services/core/java/com/android/server/adaptiveauth/OWNERS b/services/core/java/com/android/server/security/adaptiveauthentication/OWNERS index b18810564d88..b18810564d88 100644 --- a/services/core/java/com/android/server/adaptiveauth/OWNERS +++ b/services/core/java/com/android/server/security/adaptiveauthentication/OWNERS diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java index 6ce868540070..9213d96ad4ca 100644 --- a/services/core/java/com/android/server/vcn/VcnContext.java +++ b/services/core/java/com/android/server/vcn/VcnContext.java @@ -74,10 +74,6 @@ public class VcnContext { return mFeatureFlags; } - public boolean isFlagSafeModeTimeoutConfigEnabled() { - return mFeatureFlags.safeModeTimeoutConfig(); - } - /** * Verifies that the caller is running on the VcnContext Thread. * diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 2d3bc84debff..2325f358e301 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -1263,7 +1263,7 @@ public class VcnGatewayConnection extends StateMachine { final PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp); int resultSeconds = defaultSeconds; - if (vcnContext.isFlagSafeModeTimeoutConfigEnabled() && carrierConfig != null) { + if (carrierConfig != null) { resultSeconds = carrierConfig.getInt( VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, defaultSeconds); diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java index df44e50d2839..a92ac679b0f4 100644 --- a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java +++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java @@ -45,6 +45,7 @@ final class ExternalVibrationSession extends Vibration void onExternalVibrationReleased(long vibrationId); } + private final long mSessionId = VibrationSession.nextSessionId(); private final ExternalVibration mExternalVibration; private final ExternalVibrationScale mScale = new ExternalVibrationScale(); private final VibratorManagerHooks mManagerHooks; @@ -65,6 +66,11 @@ final class ExternalVibrationSession extends Vibration } @Override + public long getSessionId() { + return mSessionId; + } + + @Override public long getCreateUptimeMillis() { return stats.getCreateUptimeMillis(); } @@ -148,7 +154,12 @@ final class ExternalVibrationSession extends Vibration @Override public void notifySyncedVibratorsCallback(long vibrationId) { - // ignored, external control does not expect callbacks from the vibrator manager + // ignored, external control does not expect callbacks from the vibrator manager for sync + } + + @Override + public void notifySessionCallback() { + // ignored, external control does not expect callbacks from the vibrator manager for session } boolean isHoldingSameVibration(ExternalVibration vib) { @@ -174,7 +185,8 @@ final class ExternalVibrationSession extends Vibration @Override public String toString() { return "ExternalVibrationSession{" - + "id=" + id + + "sessionId=" + mSessionId + + ", vibrationId=" + id + ", callerInfo=" + callerInfo + ", externalVibration=" + mExternalVibration + ", scale=" + mScale diff --git a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java index 67ba25f6b0b9..628221b09d77 100644 --- a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java +++ b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java @@ -35,6 +35,7 @@ final class SingleVibrationSession implements VibrationSession, IBinder.DeathRec private static final String TAG = "SingleVibrationSession"; private final Object mLock = new Object(); + private final long mSessionId = VibrationSession.nextSessionId(); private final IBinder mCallerToken; private final HalVibration mVibration; @@ -58,6 +59,11 @@ final class SingleVibrationSession implements VibrationSession, IBinder.DeathRec } @Override + public long getSessionId() { + return mSessionId; + } + + @Override public long getCreateUptimeMillis() { return mVibration.stats.getCreateUptimeMillis(); } @@ -155,9 +161,15 @@ final class SingleVibrationSession implements VibrationSession, IBinder.DeathRec } @Override + public void notifySessionCallback() { + // ignored, external control does not expect callbacks from the vibrator manager for session + } + + @Override public String toString() { return "SingleVibrationSession{" - + "callerToken= " + mCallerToken + + "sessionId= " + mSessionId + + ", callerToken= " + mCallerToken + ", vibration=" + mVibration + '}'; } diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java new file mode 100644 index 000000000000..07478e360d27 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java @@ -0,0 +1,493 @@ +/* + * 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.vibrator; + +import static com.android.server.vibrator.VibrationSession.DebugInfo.formatTime; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.media.AudioAttributes; +import android.os.CancellationSignal; +import android.os.CombinedVibration; +import android.os.ExternalVibration; +import android.os.Handler; +import android.os.IBinder; +import android.os.ICancellationSignal; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.VibrationAttributes; +import android.os.vibrator.IVibrationSession; +import android.os.vibrator.IVibrationSessionCallback; +import android.util.IndentingPrintWriter; +import android.util.Slog; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; + +import java.util.Arrays; +import java.util.Locale; +import java.util.NoSuchElementException; + +/** + * A vibration session started by a vendor request that can trigger {@link CombinedVibration}. + */ +final class VendorVibrationSession extends IVibrationSession.Stub + implements VibrationSession, CancellationSignal.OnCancelListener, IBinder.DeathRecipient { + private static final String TAG = "VendorVibrationSession"; + + /** Calls into VibratorManager functionality needed for playing an {@link ExternalVibration}. */ + interface VibratorManagerHooks { + + /** Tells the manager to end the vibration session. */ + void endSession(long sessionId, boolean shouldAbort); + + /** + * Tells the manager that the vibration session is finished and the vibrators can now be + * used for another vibration. + */ + void onSessionReleased(long sessionId); + } + + private final Object mLock = new Object(); + private final long mSessionId = VibrationSession.nextSessionId(); + private final ICancellationSignal mCancellationSignal = CancellationSignal.createTransport(); + private final int[] mVibratorIds; + private final long mCreateUptime; + private final long mCreateTime; // for debugging + private final IVibrationSessionCallback mCallback; + private final CallerInfo mCallerInfo; + private final VibratorManagerHooks mManagerHooks; + private final Handler mHandler; + + @GuardedBy("mLock") + private Status mStatus = Status.RUNNING; + @GuardedBy("mLock") + private Status mEndStatusRequest; + @GuardedBy("mLock") + private long mStartTime; // for debugging + @GuardedBy("mLock") + private long mEndUptime; + @GuardedBy("mLock") + private long mEndTime; // for debugging + + VendorVibrationSession(@NonNull CallerInfo callerInfo, @NonNull Handler handler, + @NonNull VibratorManagerHooks managerHooks, @NonNull int[] vibratorIds, + @NonNull IVibrationSessionCallback callback) { + mCreateUptime = SystemClock.uptimeMillis(); + mCreateTime = System.currentTimeMillis(); + mVibratorIds = vibratorIds; + mHandler = handler; + mCallback = callback; + mCallerInfo = callerInfo; + mManagerHooks = managerHooks; + CancellationSignal.fromTransport(mCancellationSignal).setOnCancelListener(this); + } + + @Override + public void vibrate(CombinedVibration vibration, String reason) { + // TODO(b/345414356): implement vibration support + throw new UnsupportedOperationException("Vendor session vibrations not yet implemented"); + } + + @Override + public void finishSession() { + // Do not abort session in HAL, wait for ongoing vibration requests to complete. + // This might take a while to end the session, but it can be aborted by cancelSession. + requestEndSession(Status.FINISHED, /* shouldAbort= */ false); + } + + @Override + public void cancelSession() { + // Always abort session in HAL while cancelling it. + // This might be triggered after finishSession was already called. + requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true); + } + + @Override + public long getSessionId() { + return mSessionId; + } + + @Override + public long getCreateUptimeMillis() { + return mCreateUptime; + } + + @Override + public boolean isRepeating() { + return false; + } + + @Override + public CallerInfo getCallerInfo() { + return mCallerInfo; + } + + @Override + public IBinder getCallerToken() { + return mCallback.asBinder(); + } + + @Override + public DebugInfo getDebugInfo() { + synchronized (mLock) { + return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime, + mEndUptime, mEndTime); + } + } + + @Override + public boolean wasEndRequested() { + synchronized (mLock) { + return mEndStatusRequest != null; + } + } + + @Override + public void onCancel() { + Slog.d(TAG, "Cancellation signal received, cancelling vibration session..."); + requestEnd(Status.CANCELLED_BY_USER, /* endedBy= */ null, /* immediate= */ false); + } + + @Override + public void binderDied() { + Slog.d(TAG, "Binder died, cancelling vibration session..."); + requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false); + } + + @Override + public boolean linkToDeath() { + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + Slog.e(TAG, "Error linking session to token death", e); + return false; + } + return true; + } + + @Override + public void unlinkToDeath() { + try { + mCallback.asBinder().unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Slog.wtf(TAG, "Failed to unlink session to token death", e); + } + } + + @Override + public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy, + boolean immediate) { + // All requests to end a session should abort it to stop ongoing vibrations, even if + // immediate flag is false. Only the #finishSession API will not abort and wait for + // session vibrations to complete, which might take a long time. + requestEndSession(status, /* shouldAbort= */ true); + } + + @Override + public void notifyVibratorCallback(int vibratorId, long vibrationId) { + // TODO(b/345414356): implement vibration support + } + + @Override + public void notifySyncedVibratorsCallback(long vibrationId) { + // TODO(b/345414356): implement vibration support + } + + @Override + public void notifySessionCallback() { + synchronized (mLock) { + // If end was not requested then the HAL has cancelled the session. + maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON); + maybeSetStatusToRequestedLocked(); + } + mManagerHooks.onSessionReleased(mSessionId); + } + + @Override + public String toString() { + synchronized (mLock) { + return "createTime: " + formatTime(mCreateTime, /*includeDate=*/ true) + + ", startTime: " + (mStartTime == 0 ? null : formatTime(mStartTime, + /* includeDate= */ true)) + + ", endTime: " + (mEndTime == 0 ? null : formatTime(mEndTime, + /* includeDate= */ true)) + + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) + + ", callerInfo: " + mCallerInfo + + ", vibratorIds: " + Arrays.toString(mVibratorIds); + } + } + + public Status getStatus() { + synchronized (mLock) { + return mStatus; + } + } + + public boolean isStarted() { + synchronized (mLock) { + return mStartTime > 0; + } + } + + public boolean isEnded() { + synchronized (mLock) { + return mStatus != Status.RUNNING; + } + } + + public int[] getVibratorIds() { + return mVibratorIds; + } + + public ICancellationSignal getCancellationSignal() { + return mCancellationSignal; + } + + public void notifyStart() { + boolean isAlreadyEnded = false; + synchronized (mLock) { + if (isEnded()) { + // Session already ended, skip start callbacks. + isAlreadyEnded = true; + } else { + mStartTime = System.currentTimeMillis(); + // Run client callback in separate thread. + mHandler.post(() -> { + try { + mCallback.onStarted(this); + } catch (RemoteException e) { + Slog.e(TAG, "Error notifying vendor session started", e); + } + }); + } + } + if (isAlreadyEnded) { + // Session already ended, make sure we end it in the HAL. + mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true); + } + } + + private void requestEndSession(Status status, boolean shouldAbort) { + boolean shouldTriggerSessionHook = false; + synchronized (mLock) { + maybeSetEndRequestLocked(status); + if (isStarted()) { + // Always trigger session hook after it has started, in case new request aborts an + // already finishing session. Wait for HAL callback before actually ending here. + shouldTriggerSessionHook = true; + } else { + // Session did not start in the HAL, end it right away. + maybeSetStatusToRequestedLocked(); + } + } + if (shouldTriggerSessionHook) { + mManagerHooks.endSession(mSessionId, shouldAbort); + } + } + + @GuardedBy("mLock") + private void maybeSetEndRequestLocked(Status status) { + if (mEndStatusRequest != null) { + // End already requested, keep first requested status and time. + return; + } + mEndStatusRequest = status; + mEndTime = System.currentTimeMillis(); + mEndUptime = SystemClock.uptimeMillis(); + if (isStarted()) { + // Only trigger "finishing" callback if session started. + // Run client callback in separate thread. + mHandler.post(() -> { + try { + mCallback.onFinishing(); + } catch (RemoteException e) { + Slog.e(TAG, "Error notifying vendor session is finishing", e); + } + }); + } + } + + @GuardedBy("mLock") + private void maybeSetStatusToRequestedLocked() { + if (isEnded()) { + // End already set, keep first requested status and time. + return; + } + if (mEndStatusRequest == null) { + // No end status was requested, nothing to set. + return; + } + mStatus = mEndStatusRequest; + // Run client callback in separate thread. + final Status endStatus = mStatus; + mHandler.post(() -> { + try { + mCallback.onFinished(toSessionStatus(endStatus)); + } catch (RemoteException e) { + Slog.e(TAG, "Error notifying vendor session is finishing", e); + } + }); + } + + @android.os.vibrator.VendorVibrationSession.Status + private static int toSessionStatus(Status status) { + // Exhaustive switch to cover all possible internal status. + return switch (status) { + case FINISHED + -> android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS; + case IGNORED_UNSUPPORTED + -> STATUS_UNSUPPORTED; + case CANCELLED_BINDER_DIED, CANCELLED_BY_APP_OPS, CANCELLED_BY_USER, + CANCELLED_SUPERSEDED, CANCELLED_BY_FOREGROUND_USER, CANCELLED_BY_SCREEN_OFF, + CANCELLED_BY_SETTINGS_UPDATE, CANCELLED_BY_UNKNOWN_REASON + -> android.os.vibrator.VendorVibrationSession.STATUS_CANCELED; + case IGNORED_APP_OPS, IGNORED_BACKGROUND, IGNORED_FOR_EXTERNAL, IGNORED_FOR_ONGOING, + IGNORED_FOR_POWER, IGNORED_FOR_SETTINGS, IGNORED_FOR_HIGHER_IMPORTANCE, + IGNORED_FOR_RINGER_MODE, IGNORED_FROM_VIRTUAL_DEVICE, IGNORED_SUPERSEDED, + IGNORED_MISSING_PERMISSION, IGNORED_ON_WIRELESS_CHARGER + -> android.os.vibrator.VendorVibrationSession.STATUS_IGNORED; + case UNKNOWN, IGNORED_ERROR_APP_OPS, IGNORED_ERROR_CANCELLING, IGNORED_ERROR_SCHEDULING, + IGNORED_ERROR_TOKEN, FORWARDED_TO_INPUT_DEVICES, FINISHED_UNEXPECTED, RUNNING + -> android.os.vibrator.VendorVibrationSession.STATUS_UNKNOWN_ERROR; + }; + } + + /** + * Holds lightweight debug information about the session that could potentially be kept in + * memory for a long time for bugreport dumpsys operations. + * + * Since DebugInfo can be kept in memory for a long time, it shouldn't hold any references to + * potentially expensive or resource-linked objects, such as {@link IBinder}. + */ + static final class DebugInfoImpl implements VibrationSession.DebugInfo { + private final Status mStatus; + private final CallerInfo mCallerInfo; + + private final long mCreateUptime; + private final long mCreateTime; + private final long mStartTime; + private final long mEndTime; + private final long mDurationMs; + + DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime, + long startTime, long endUptime, long endTime) { + mStatus = status; + mCallerInfo = callerInfo; + mCreateUptime = createUptime; + mCreateTime = createTime; + mStartTime = startTime; + mEndTime = endTime; + mDurationMs = endUptime > 0 ? endUptime - createUptime : -1; + } + + @Override + public Status getStatus() { + return mStatus; + } + + @Override + public long getCreateUptimeMillis() { + return mCreateUptime; + } + + @Override + public CallerInfo getCallerInfo() { + return mCallerInfo; + } + + @Nullable + @Override + public Object getDumpAggregationKey() { + return null; // No aggregation. + } + + @Override + public void logMetrics(VibratorFrameworkStatsLogger statsLogger) { + } + + @Override + public void dump(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(VibrationProto.END_TIME, mEndTime); + proto.write(VibrationProto.DURATION_MS, mDurationMs); + proto.write(VibrationProto.STATUS, mStatus.ordinal()); + + final long attrsToken = proto.start(VibrationProto.ATTRIBUTES); + final VibrationAttributes attrs = mCallerInfo.attrs; + proto.write(VibrationAttributesProto.USAGE, attrs.getUsage()); + proto.write(VibrationAttributesProto.AUDIO_USAGE, attrs.getAudioUsage()); + proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags()); + proto.end(attrsToken); + + proto.end(token); + } + + @Override + public void dump(IndentingPrintWriter pw) { + pw.println("VibrationSession:"); + pw.increaseIndent(); + pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT)); + pw.println("durationMs = " + mDurationMs); + pw.println("createTime = " + formatTime(mCreateTime, /*includeDate=*/ true)); + pw.println("startTime = " + formatTime(mStartTime, /*includeDate=*/ true)); + pw.println("endTime = " + (mEndTime == 0 ? null + : formatTime(mEndTime, /*includeDate=*/ true))); + pw.println("callerInfo = " + mCallerInfo); + pw.decreaseIndent(); + } + + @Override + public void dumpCompact(IndentingPrintWriter pw) { + // Follow pattern from Vibration.DebugInfoImpl for better debugging from dumpsys. + String timingsStr = String.format(Locale.ROOT, + "%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s", + formatTime(mCreateTime, /*includeDate=*/ true), + "session", + mStatus.name().toLowerCase(Locale.ROOT), + mDurationMs, + mStartTime == 0 ? "" : formatTime(mStartTime, /*includeDate=*/ false), + mEndTime == 0 ? "" : formatTime(mEndTime, /*includeDate=*/ false)); + String paramStr = String.format(Locale.ROOT, + " | flags: %4s | usage: %s", + Long.toBinaryString(mCallerInfo.attrs.getFlags()), + mCallerInfo.attrs.usageToString()); + // Optional, most vibrations should not be defined via AudioAttributes + // so skip them to simplify the logs + String audioUsageStr = + mCallerInfo.attrs.getOriginalAudioUsage() != AudioAttributes.USAGE_UNKNOWN + ? " | audioUsage=" + AudioAttributes.usageToString( + mCallerInfo.attrs.getOriginalAudioUsage()) + : ""; + String callerStr = String.format(Locale.ROOT, + " | %s (uid=%d, deviceId=%d) | reason: %s", + mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason); + pw.println(timingsStr + paramStr + audioUsageStr + callerStr); + } + + @Override + public String toString() { + return "createTime: " + formatTime(mCreateTime, /* includeDate= */ true) + + ", startTime: " + formatTime(mStartTime, /* includeDate= */ true) + + ", endTime: " + (mEndTime == 0 ? null : formatTime(mEndTime, + /* includeDate= */ true)) + + ", durationMs: " + mDurationMs + + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) + + ", callerInfo: " + mCallerInfo; + } + } +} diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index bb2a17c698ee..27f92b2080e6 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -16,6 +16,8 @@ package com.android.server.vibrator; +import static com.android.server.vibrator.VibrationSession.DebugInfo.formatTime; + import android.annotation.NonNull; import android.annotation.Nullable; import android.media.AudioAttributes; @@ -31,9 +33,6 @@ import android.os.vibrator.VibrationEffectSegment; import android.util.IndentingPrintWriter; import android.util.proto.ProtoOutputStream; -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; import java.util.Locale; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; @@ -42,11 +41,6 @@ import java.util.concurrent.atomic.AtomicInteger; * The base class for all vibrations. */ abstract class Vibration { - private static final DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern( - "HH:mm:ss.SSS"); - private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern( - "MM-dd HH:mm:ss.SSS"); - // Used to generate globally unique vibration ids. private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback @@ -399,12 +393,5 @@ abstract class Vibration { proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay()); proto.end(token); } - - private String formatTime(long timeInMillis, boolean includeDate) { - return (includeDate ? DEBUG_DATE_TIME_FORMATTER : DEBUG_TIME_FORMATTER) - // Ensure timezone is retrieved at formatting time - .withZone(ZoneId.systemDefault()) - .format(Instant.ofEpochMilli(timeInMillis)); - } } } diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java index b511ba8be405..ae95a70e2a4f 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSession.java +++ b/services/core/java/com/android/server/vibrator/VibrationSession.java @@ -25,7 +25,11 @@ import android.util.IndentingPrintWriter; import android.util.proto.ProtoOutputStream; import java.io.PrintWriter; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; /** * Represents a generic vibration session that plays one or more vibration requests. @@ -39,6 +43,16 @@ import java.util.Objects; */ interface VibrationSession { + // Used to generate globally unique session ids. + AtomicInteger sNextSessionId = new AtomicInteger(1); // 0 = no callback + + static long nextSessionId() { + return sNextSessionId.getAndIncrement(); + } + + /** Returns the session id. */ + long getSessionId(); + /** Returns the session creation time from {@link android.os.SystemClock#uptimeMillis()}. */ long getCreateUptimeMillis(); @@ -105,6 +119,14 @@ interface VibrationSession { void notifySyncedVibratorsCallback(long vibrationId); /** + * Notify vibrator manager have completed the vibration session. + * + * <p>This will be called by the vibrator manager hardware callback indicating the session + * is complete, either because it was ended or cancelled by the service or the vendor. + */ + void notifySessionCallback(); + + /** * Session status with reference to values from vibratormanagerservice.proto for logging. */ enum Status { @@ -212,6 +234,17 @@ interface VibrationSession { */ interface DebugInfo { + DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); + DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern( + "MM-dd HH:mm:ss.SSS"); + + static String formatTime(long timeInMillis, boolean includeDate) { + return (includeDate ? DEBUG_DATE_TIME_FORMATTER : DEBUG_TIME_FORMATTER) + // Ensure timezone is retrieved at formatting time + .withZone(ZoneId.systemDefault()) + .format(Instant.ofEpochMilli(timeInMillis)); + } + /** Return the vibration session status. */ Status getStatus(); diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index ff3491182a5f..476448148e28 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -32,6 +32,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.hardware.vibrator.IVibrator; +import android.hardware.vibrator.IVibratorManager; import android.os.BatteryStats; import android.os.Binder; import android.os.Build; @@ -40,6 +41,7 @@ import android.os.ExternalVibration; import android.os.ExternalVibrationScale; import android.os.Handler; import android.os.IBinder; +import android.os.ICancellationSignal; import android.os.IExternalVibratorService; import android.os.IVibratorManagerService; import android.os.IVibratorStateListener; @@ -57,6 +59,7 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.VibratorInfo; import android.os.vibrator.Flags; +import android.os.vibrator.IVibrationSessionCallback; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.VibrationConfig; import android.os.vibrator.VibrationEffectSegment; @@ -103,7 +106,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service"; private static final String VIBRATOR_CONTROL_SERVICE = "android.frameworks.vibrator.IVibratorControlService/default"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; private static final VibrationAttributes DEFAULT_ATTRIBUTES = new VibrationAttributes.Builder().build(); private static final int ATTRIBUTES_ALL_BYPASS_FLAGS = @@ -159,12 +162,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { new VibrationThreadCallbacks(); private final ExternalVibrationCallbacks mExternalVibrationCallbacks = new ExternalVibrationCallbacks(); + private final VendorVibrationSessionCallbacks mVendorVibrationSessionCallbacks = + new VendorVibrationSessionCallbacks(); @GuardedBy("mLock") private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>(); @GuardedBy("mLock") - private VibrationSession mCurrentVibration; + private VibrationSession mCurrentSession; @GuardedBy("mLock") - private VibrationSession mNextVibration; + private VibrationSession mNextSession; @GuardedBy("mLock") private boolean mServiceReady; @@ -191,14 +196,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // When the system is entering a non-interactive state, we want to cancel // vibrations in case a misbehaving app has abandoned them. synchronized (mLock) { - maybeClearCurrentAndNextVibrationsLocked( + maybeClearCurrentAndNextSessionsLocked( VibratorManagerService.this::shouldCancelOnScreenOffLocked, Status.CANCELLED_BY_SCREEN_OFF); } } else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers() && intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) { synchronized (mLock) { - maybeClearCurrentAndNextVibrationsLocked( + maybeClearCurrentAndNextSessionsLocked( VibratorManagerService.this::shouldCancelOnFgUserRequest, Status.CANCELLED_BY_FOREGROUND_USER); } @@ -215,14 +220,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return; } synchronized (mLock) { - maybeClearCurrentAndNextVibrationsLocked( + maybeClearCurrentAndNextSessionsLocked( VibratorManagerService.this::shouldCancelAppOpModeChangedLocked, Status.CANCELLED_BY_APP_OPS); } } }; - static native long nativeInit(OnSyncedVibrationCompleteListener listener); + static native long nativeInit(VibratorManagerNativeCallbacks listener); static native long nativeGetFinalizer(); @@ -236,6 +241,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { static native void nativeCancelSynced(long nativeServicePtr); + static native boolean nativeStartSession(long nativeServicePtr, long sessionId, + int[] vibratorIds); + + static native void nativeEndSession(long nativeServicePtr, long sessionId, boolean shouldAbort); + + static native void nativeClearSessions(long nativeServicePtr); + @VisibleForTesting VibratorManagerService(Context context, Injector injector) { mContext = context; @@ -303,6 +315,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // Reset the hardware to a default state, in case this is a runtime restart instead of a // fresh boot. mNativeWrapper.cancelSynced(); + if (Flags.vendorVibrationEffects()) { + mNativeWrapper.clearSessions(); + } for (int i = 0; i < mVibrators.size(); i++) { mVibrators.valueAt(i).reset(); } @@ -363,6 +378,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @Override // Binder call + public int getCapabilities() { + return (int) mCapabilities; + } + + @Override // Binder call @Nullable public VibratorInfo getVibratorInfo(int vibratorId) { final VibratorController controller = mVibrators.get(vibratorId); @@ -590,11 +610,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_ERROR_TOKEN); return null; } - if (effect.hasVendorEffects() - && !hasPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)) { - Slog.e(TAG, "vibrate; no permission for vendor effects"); - logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_MISSING_PERMISSION); - return null; + if (effect.hasVendorEffects()) { + if (!Flags.vendorVibrationEffects()) { + Slog.e(TAG, "vibrate; vendor effects feature disabled"); + logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_UNSUPPORTED); + return null; + } + if (!hasPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)) { + Slog.e(TAG, "vibrate; no permission for vendor effects"); + logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_MISSING_PERMISSION); + return null; + } } enforceUpdateAppOpsStatsPermission(uid); if (!isEffectValid(effect)) { @@ -623,7 +649,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // Check if ongoing vibration is more important than this vibration. if (ignoreStatus == null) { - Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(session); + Vibration.EndInfo vibrationEndInfo = shouldIgnoreForOngoingLocked(session); if (vibrationEndInfo != null) { ignoreStatus = vibrationEndInfo.status; ignoredBy = vibrationEndInfo.endedBy; @@ -634,8 +660,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (ignoreStatus == null) { final long ident = Binder.clearCallingIdentity(); try { - if (mCurrentVibration != null) { - if (shouldPipelineVibrationLocked(mCurrentVibration, vib)) { + if (mCurrentSession != null) { + if (shouldPipelineVibrationLocked(mCurrentSession, vib)) { // Don't cancel the current vibration if it's pipeline-able. // Note that if there is a pending next vibration that can't be // pipelined, it will have already cancelled the current one, so we @@ -645,12 +671,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } else { vib.stats.reportInterruptedAnotherVibration( - mCurrentVibration.getCallerInfo()); - mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo, + mCurrentSession.getCallerInfo()); + mCurrentSession.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo, /* immediate= */ false); } } - clearNextVibrationLocked(Status.CANCELLED_SUPERSEDED, callerInfo); + clearNextSessionLocked(Status.CANCELLED_SUPERSEDED, callerInfo); ignoreStatus = startVibrationLocked(session); } finally { Binder.restoreCallingIdentity(ident); @@ -659,7 +685,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // Ignored or failed to start the vibration, end it and report metrics right away. if (ignoreStatus != null) { - endVibrationLocked(session, ignoreStatus, ignoredBy); + endSessionLocked(session, ignoreStatus, ignoredBy); } return vib; } @@ -681,19 +707,154 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { try { // TODO(b/370948466): investigate why token not checked on external vibrations. IBinder cancelToken = - (mNextVibration instanceof ExternalVibrationSession) ? null : token; - if (shouldCancelVibration(mNextVibration, usageFilter, cancelToken)) { - clearNextVibrationLocked(Status.CANCELLED_BY_USER); + (mNextSession instanceof ExternalVibrationSession) ? null : token; + if (shouldCancelSession(mNextSession, usageFilter, cancelToken)) { + clearNextSessionLocked(Status.CANCELLED_BY_USER); } cancelToken = - (mCurrentVibration instanceof ExternalVibrationSession) ? null : token; - if (shouldCancelVibration(mCurrentVibration, usageFilter, cancelToken)) { - mCurrentVibration.requestEnd(Status.CANCELLED_BY_USER); + (mCurrentSession instanceof ExternalVibrationSession) ? null : token; + if (shouldCancelSession(mCurrentSession, usageFilter, cancelToken)) { + mCurrentSession.requestEnd(Status.CANCELLED_BY_USER); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } finally { + Trace.traceEnd(TRACE_TAG_VIBRATOR); + } + } + + @android.annotation.EnforcePermission(allOf = { + android.Manifest.permission.VIBRATE, + android.Manifest.permission.VIBRATE_VENDOR_EFFECTS, + android.Manifest.permission.START_VIBRATION_SESSIONS, + }) + @Override // Binder call + public ICancellationSignal startVendorVibrationSession(int uid, int deviceId, String opPkg, + int[] vibratorIds, VibrationAttributes attrs, String reason, + IVibrationSessionCallback callback) { + startVendorVibrationSession_enforcePermission(); + Trace.traceBegin(TRACE_TAG_VIBRATOR, "startVibrationSession"); + try { + VendorVibrationSession session = startVendorVibrationSessionInternal( + uid, deviceId, opPkg, vibratorIds, attrs, reason, callback); + return session == null ? null : session.getCancellationSignal(); + } finally { + Trace.traceEnd(TRACE_TAG_VIBRATOR); + } + } + + VendorVibrationSession startVendorVibrationSessionInternal(int uid, int deviceId, String opPkg, + int[] vibratorIds, VibrationAttributes attrs, String reason, + IVibrationSessionCallback callback) { + if (!Flags.vendorVibrationEffects()) { + throw new UnsupportedOperationException("Vibration sessions not supported"); + } + attrs = fixupVibrationAttributes(attrs, /* effect= */ null); + CallerInfo callerInfo = new CallerInfo(attrs, uid, deviceId, opPkg, reason); + if (callback == null) { + Slog.e(TAG, "session callback must not be null"); + logAndRecordSessionAttempt(callerInfo, Status.IGNORED_ERROR_TOKEN); + return null; + } + if (vibratorIds == null) { + vibratorIds = new int[0]; + } + enforceUpdateAppOpsStatsPermission(uid); + VendorVibrationSession session = new VendorVibrationSession(callerInfo, mHandler, + mVendorVibrationSessionCallbacks, vibratorIds, callback); + + if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { + // Force update of user settings before checking if this vibration effect should + // be ignored or scaled. + mVibrationSettings.update(); + } + + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "Starting session " + session.getSessionId()); + } + + Status ignoreStatus = null; + CallerInfo ignoredBy = null; + + // Check if HAL has capability to start sessions. + if ((mCapabilities & IVibratorManager.CAP_START_SESSIONS) == 0) { + if (DEBUG) { + Slog.d(TAG, "Missing capability to start sessions, ignoring request"); + } + ignoreStatus = Status.IGNORED_UNSUPPORTED; + } + + // Check if any vibrator ID was requested. + if (ignoreStatus == null && vibratorIds.length == 0) { + if (DEBUG) { + Slog.d(TAG, "Empty vibrator ids to start session, ignoring request"); + } + ignoreStatus = Status.IGNORED_UNSUPPORTED; + } + + // Check if user settings or DnD is set to ignore this session. + if (ignoreStatus == null) { + ignoreStatus = shouldIgnoreVibrationLocked(callerInfo); + } + + // Check if ongoing vibration is more important than this session. + if (ignoreStatus == null) { + Vibration.EndInfo vibrationEndInfo = shouldIgnoreForOngoingLocked(session); + if (vibrationEndInfo != null) { + ignoreStatus = vibrationEndInfo.status; + ignoredBy = vibrationEndInfo.endedBy; + } + } + + if (ignoreStatus == null) { + final long ident = Binder.clearCallingIdentity(); + try { + // If not ignored so far then stop ongoing sessions before starting this one. + clearNextSessionLocked(Status.CANCELLED_SUPERSEDED, callerInfo); + if (mCurrentSession != null) { + mNextSession = session; + mCurrentSession.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo, + /* immediate= */ false); + } else { + ignoreStatus = startVendorSessionLocked(session); } } finally { Binder.restoreCallingIdentity(ident); } } + + // Ignored or failed to start the session, end it and report metrics right away. + if (ignoreStatus != null) { + endSessionLocked(session, ignoreStatus, ignoredBy); + } + return session; + } + } + + @GuardedBy("mLock") + @Nullable + private Status startVendorSessionLocked(VendorVibrationSession session) { + Trace.traceBegin(TRACE_TAG_VIBRATOR, "startSessionLocked"); + try { + if (session.isEnded()) { + // Session already ended, possibly cancelled by app cancellation signal. + return session.getStatus(); + } + if (!session.linkToDeath()) { + return Status.IGNORED_ERROR_TOKEN; + } + if (!mNativeWrapper.startSession(session.getSessionId(), session.getVibratorIds())) { + Slog.e(TAG, "Error starting session " + session.getSessionId() + + " on vibrators " + Arrays.toString(session.getVibratorIds())); + session.unlinkToDeath(); + return Status.IGNORED_UNSUPPORTED; + } + session.notifyStart(); + mCurrentSession = session; + return null; } finally { Trace.traceEnd(TRACE_TAG_VIBRATOR); } @@ -747,8 +908,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { pw.println("CurrentVibration:"); pw.increaseIndent(); - if (mCurrentVibration != null) { - mCurrentVibration.getDebugInfo().dump(pw); + if (mCurrentSession != null) { + mCurrentSession.getDebugInfo().dump(pw); } else { pw.println("null"); } @@ -757,8 +918,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { pw.println("NextVibration:"); pw.increaseIndent(); - if (mNextVibration != null) { - mNextVibration.getDebugInfo().dump(pw); + if (mNextSession != null) { + mNextSession.getDebugInfo().dump(pw); } else { pw.println("null"); } @@ -782,8 +943,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { synchronized (mLock) { mVibrationSettings.dump(proto); mVibrationScaler.dump(proto); - if (mCurrentVibration != null) { - mCurrentVibration.getDebugInfo().dump(proto, + if (mCurrentSession != null) { + mCurrentSession.getDebugInfo().dump(proto, VibratorManagerServiceDumpProto.CURRENT_VIBRATION); } for (int i = 0; i < mVibrators.size(); i++) { @@ -816,18 +977,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } // TODO(b/372241975): investigate why external vibrations were not handled here before - if (mCurrentVibration == null - || (mCurrentVibration instanceof ExternalVibrationSession)) { + if (mCurrentSession == null + || (mCurrentSession instanceof ExternalVibrationSession)) { return; } - Status ignoreStatus = shouldIgnoreVibrationLocked(mCurrentVibration.getCallerInfo()); + Status ignoreStatus = shouldIgnoreVibrationLocked(mCurrentSession.getCallerInfo()); if (inputDevicesChanged || (ignoreStatus != null)) { if (DEBUG) { Slog.d(TAG, "Canceling vibration because settings changed: " + (inputDevicesChanged ? "input devices changed" : ignoreStatus)); } - mCurrentVibration.requestEnd(Status.CANCELLED_BY_SETTINGS_UPDATE); + mCurrentSession.requestEnd(Status.CANCELLED_BY_SETTINGS_UPDATE); } } } @@ -866,15 +1027,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (mInputDeviceDelegate.isAvailable()) { return startVibrationOnInputDevicesLocked(session.getVibration()); } - if (mCurrentVibration == null) { + if (mCurrentSession == null) { return startVibrationOnThreadLocked(session); } // If there's already a vibration queued (waiting for the previous one to finish // cancelling), end it cleanly and replace it with the new one. // Note that we don't consider pipelining here, because new pipelined ones should // replace pending non-executing pipelined ones anyway. - clearNextVibrationLocked(Status.IGNORED_SUPERSEDED, session.getCallerInfo()); - mNextVibration = session; + clearNextSessionLocked(Status.IGNORED_SUPERSEDED, session.getCallerInfo()); + mNextSession = session; return null; } finally { Trace.traceEnd(TRACE_TAG_VIBRATOR); @@ -891,16 +1052,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { case AppOpsManager.MODE_ALLOWED: Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0); // Make sure mCurrentVibration is set while triggering the VibrationThread. - mCurrentVibration = session; - if (!mCurrentVibration.linkToDeath()) { + mCurrentSession = session; + if (!mCurrentSession.linkToDeath()) { // Shouldn't happen. The method call already logs. - mCurrentVibration = null; // Aborted. + mCurrentSession = null; // Aborted. return Status.IGNORED_ERROR_TOKEN; } if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) { // Shouldn't happen. The method call already logs. session.setVibrationConductor(null); // Rejected by thread, clear it in session. - mCurrentVibration = null; // Aborted. + mCurrentSession = null; // Aborted. return Status.IGNORED_ERROR_SCHEDULING; } return null; @@ -914,23 +1075,29 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @GuardedBy("mLock") - private void maybeStartNextSingleVibrationLocked() { - if (mNextVibration instanceof SingleVibrationSession session) { - mNextVibration = null; + private void maybeStartNextSessionLocked() { + if (mNextSession instanceof SingleVibrationSession session) { + mNextSession = null; Status errorStatus = startVibrationOnThreadLocked(session); if (errorStatus != null) { - endVibrationLocked(session, errorStatus); + endSessionLocked(session, errorStatus); } - } + } else if (mNextSession instanceof VendorVibrationSession session) { + mNextSession = null; + Status errorStatus = startVendorSessionLocked(session); + if (errorStatus != null) { + endSessionLocked(session, errorStatus); + } + } // External vibrations cannot be started asynchronously. } @GuardedBy("mLock") - private void endVibrationLocked(VibrationSession session, Status status) { - endVibrationLocked(session, status, /* endedBy= */ null); + private void endSessionLocked(VibrationSession session, Status status) { + endSessionLocked(session, status, /* endedBy= */ null); } @GuardedBy("mLock") - private void endVibrationLocked(VibrationSession session, Status status, CallerInfo endedBy) { + private void endSessionLocked(VibrationSession session, Status status, CallerInfo endedBy) { session.requestEnd(status, endedBy, /* immediate= */ false); logAndRecordVibration(session.getDebugInfo()); } @@ -975,6 +1142,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { VibrationScaler.ADAPTIVE_SCALE_NONE)); } + private void logAndRecordSessionAttempt(CallerInfo callerInfo, Status status) { + logAndRecordVibration( + new VendorVibrationSession.DebugInfoImpl(status, callerInfo, + SystemClock.uptimeMillis(), System.currentTimeMillis(), + /* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0)); + } + private void logAndRecordVibration(DebugInfo info) { info.logMetrics(mFrameworkStatsLogger); logVibrationStatus(info.getCallerInfo().uid, info.getCallerInfo().attrs, info.getStatus()); @@ -1026,25 +1200,40 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + private void onVibrationSessionComplete(long sessionId) { + synchronized (mLock) { + if (mCurrentSession == null || mCurrentSession.getSessionId() != sessionId) { + if (DEBUG) { + Slog.d(TAG, "Vibration session " + sessionId + " callback ignored"); + } + return; + } + if (DEBUG) { + Slog.d(TAG, "Vibration session " + sessionId + " complete, notifying session"); + } + mCurrentSession.notifySessionCallback(); + } + } + private void onSyncedVibrationComplete(long vibrationId) { synchronized (mLock) { - if (mCurrentVibration != null) { + if (mCurrentSession != null) { if (DEBUG) { Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread"); } - mCurrentVibration.notifySyncedVibratorsCallback(vibrationId); + mCurrentSession.notifySyncedVibratorsCallback(vibrationId); } } } private void onVibrationComplete(int vibratorId, long vibrationId) { synchronized (mLock) { - if (mCurrentVibration != null) { + if (mCurrentSession != null) { if (DEBUG) { Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId + " complete, notifying thread"); } - mCurrentVibration.notifyVibratorCallback(vibratorId, vibrationId); + mCurrentSession.notifyVibratorCallback(vibratorId, vibrationId); } } } @@ -1056,10 +1245,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ @GuardedBy("mLock") @Nullable - private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(VibrationSession session) { - if (mNextVibration != null) { - Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(session, - mNextVibration); + private Vibration.EndInfo shouldIgnoreForOngoingLocked(VibrationSession session) { + if (mNextSession != null) { + Vibration.EndInfo vibrationEndInfo = shouldIgnoreForOngoing(session, + mNextSession); if (vibrationEndInfo != null) { // Next vibration has higher importance than the new one, so the new vibration // should be ignored. @@ -1067,13 +1256,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } - if (mCurrentVibration != null) { - if (mCurrentVibration.wasEndRequested()) { + if (mCurrentSession != null) { + if (mCurrentSession.wasEndRequested()) { // Current session has ended or is cancelling, should not block incoming vibrations. return null; } - return shouldIgnoreVibrationForOngoing(session, mCurrentVibration); + return shouldIgnoreForOngoing(session, mCurrentSession); } return null; @@ -1086,7 +1275,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { * @return a Vibration.EndInfo if the vibration should be ignored, null otherwise. */ @Nullable - private static Vibration.EndInfo shouldIgnoreVibrationForOngoing( + private static Vibration.EndInfo shouldIgnoreForOngoing( @NonNull VibrationSession newSession, @NonNull VibrationSession ongoingSession) { int newSessionImportance = getVibrationImportance(newSession); @@ -1214,11 +1403,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { * @param tokenFilter The binder token to identify the vibration origin. Only vibrations * started with the same token can be cancelled with it. */ - private boolean shouldCancelVibration(@Nullable VibrationSession session, int usageFilter, + private boolean shouldCancelSession(@Nullable VibrationSession session, int usageFilter, @Nullable IBinder tokenFilter) { if (session == null) { return false; } + if (session instanceof VendorVibrationSession) { + // Vendor sessions should not be cancelled by Vibrator.cancel API. + return false; + } if ((tokenFilter != null) && (tokenFilter != session.getCallerToken())) { // Vibration from a different app, this should not cancel it. return false; @@ -1572,10 +1765,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Trace.traceBegin(TRACE_TAG_VIBRATOR, "onVibrationThreadReleased"); try { synchronized (mLock) { - if (!(mCurrentVibration instanceof SingleVibrationSession session)) { + if (!(mCurrentSession instanceof SingleVibrationSession session)) { if (Build.IS_DEBUGGABLE) { Slog.wtf(TAG, "VibrationSession invalid on vibration thread release." - + " currentSession=" + mCurrentVibration); + + " currentSession=" + mCurrentSession); } // Only single vibration sessions are ended by thread being released. Abort. return; @@ -1586,11 +1779,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { + " expected=%d, released=%d", session.getVibration().id, vibrationId)); } - finishAppOpModeLocked(mCurrentVibration.getCallerInfo()); - clearCurrentVibrationLocked(); + finishAppOpModeLocked(mCurrentSession.getCallerInfo()); + clearCurrentSessionLocked(); Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); - // Start next vibration if it's a single vibration waiting for the thread. - maybeStartNextSingleVibrationLocked(); + // Start next vibration if it's waiting for the thread. + maybeStartNextSessionLocked(); } } finally { Trace.traceEnd(TRACE_TAG_VIBRATOR); @@ -1613,10 +1806,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Trace.traceBegin(TRACE_TAG_VIBRATOR, "onExternalVibrationReleased"); try { synchronized (mLock) { - if (!(mCurrentVibration instanceof ExternalVibrationSession session)) { + if (!(mCurrentSession instanceof ExternalVibrationSession session)) { if (Build.IS_DEBUGGABLE) { Slog.wtf(TAG, "VibrationSession invalid on external vibration release." - + " currentSession=" + mCurrentVibration); + + " currentSession=" + mCurrentSession); } // Only external vibration sessions are ended by this callback. Abort. return; @@ -1627,10 +1820,62 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { + " expected=%d, released=%d", session.id, vibrationId)); } setExternalControl(false, session.stats); - clearCurrentVibrationLocked(); - // Start next vibration if it's a single vibration waiting for the external - // control to be over. - maybeStartNextSingleVibrationLocked(); + clearCurrentSessionLocked(); + // Start next vibration if it's waiting for the external control to be over. + maybeStartNextSessionLocked(); + } + } finally { + Trace.traceEnd(TRACE_TAG_VIBRATOR); + } + } + } + + /** + * Implementation of {@link ExternalVibrationSession.VibratorManagerHooks} that controls + * external vibrations and reports them when finished. + */ + private final class VendorVibrationSessionCallbacks + implements VendorVibrationSession.VibratorManagerHooks { + + @Override + public void endSession(long sessionId, boolean shouldAbort) { + if (DEBUG) { + Slog.d(TAG, "Vibration session " + sessionId + + (shouldAbort ? " aborting" : " ending")); + } + Trace.traceBegin(TRACE_TAG_VIBRATOR, "endSession"); + try { + mNativeWrapper.endSession(sessionId, shouldAbort); + } finally { + Trace.traceEnd(TRACE_TAG_VIBRATOR); + } + } + + @Override + public void onSessionReleased(long sessionId) { + if (DEBUG) { + Slog.d(TAG, "Vibration session " + sessionId + " released"); + } + Trace.traceBegin(TRACE_TAG_VIBRATOR, "onVendorSessionReleased"); + try { + synchronized (mLock) { + if (!(mCurrentSession instanceof VendorVibrationSession session)) { + if (Build.IS_DEBUGGABLE) { + Slog.wtf(TAG, "VibrationSession invalid on vibration session release." + + " currentSession=" + mCurrentSession); + } + // Only vendor vibration sessions are ended by this callback. Abort. + return; + } + if (Build.IS_DEBUGGABLE && (session.getSessionId() != sessionId)) { + Slog.wtf(TAG, TextUtils.formatSimple( + "SessionId mismatch on vendor vibration session release." + + " expected=%d, released=%d", + session.getSessionId(), sessionId)); + } + clearCurrentSessionLocked(); + // Start next vibration if it's waiting for the HAL session to be over. + maybeStartNextSessionLocked(); } } finally { Trace.traceEnd(TRACE_TAG_VIBRATOR); @@ -1638,19 +1883,22 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } - /** Listener for synced vibration completion callbacks from native. */ + /** Listener for vibrator manager completion callbacks from native. */ @VisibleForTesting - interface OnSyncedVibrationCompleteListener { + interface VibratorManagerNativeCallbacks { /** Callback triggered when synced vibration is complete. */ - void onComplete(long vibrationId); + void onSyncedVibrationComplete(long vibrationId); + + /** Callback triggered when vibration session is complete. */ + void onVibrationSessionComplete(long sessionId); } /** * Implementation of listeners to native vibrators with a weak reference to this service. */ private static final class VibrationCompleteListener implements - VibratorController.OnVibrationCompleteListener, OnSyncedVibrationCompleteListener { + VibratorController.OnVibrationCompleteListener, VibratorManagerNativeCallbacks { private WeakReference<VibratorManagerService> mServiceRef; VibrationCompleteListener(VibratorManagerService service) { @@ -1658,7 +1906,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @Override - public void onComplete(long vibrationId) { + public void onSyncedVibrationComplete(long vibrationId) { VibratorManagerService service = mServiceRef.get(); if (service != null) { service.onSyncedVibrationComplete(vibrationId); @@ -1666,6 +1914,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @Override + public void onVibrationSessionComplete(long sessionId) { + VibratorManagerService service = mServiceRef.get(); + if (service != null) { + service.onVibrationSessionComplete(sessionId); + } + } + + @Override public void onComplete(int vibratorId, long vibrationId) { VibratorManagerService service = mServiceRef.get(); if (service != null) { @@ -1698,7 +1954,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private long mNativeServicePtr = 0; /** Returns native pointer to newly created controller and connects with HAL service. */ - public void init(OnSyncedVibrationCompleteListener listener) { + public void init(VibratorManagerNativeCallbacks listener) { mNativeServicePtr = nativeInit(listener); long finalizerPtr = nativeGetFinalizer(); @@ -1734,6 +1990,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { public void cancelSynced() { nativeCancelSynced(mNativeServicePtr); } + + /** Start vibration session. */ + public boolean startSession(long sessionId, @NonNull int[] vibratorIds) { + return nativeStartSession(mNativeServicePtr, sessionId, vibratorIds); + } + + /** End vibration session. */ + public void endSession(long sessionId, boolean shouldAbort) { + nativeEndSession(mNativeServicePtr, sessionId, shouldAbort); + } + + /** Clear vibration sessions. */ + public void clearSessions() { + nativeClearSessions(mNativeServicePtr); + } } /** Keep records of vibrations played and provide debug information for this service. */ @@ -1853,46 +2124,46 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { /** Clears mNextVibration if set, ending it cleanly */ @GuardedBy("mLock") - private void clearNextVibrationLocked(Status status) { - clearNextVibrationLocked(status, /* endedBy= */ null); + private void clearNextSessionLocked(Status status) { + clearNextSessionLocked(status, /* endedBy= */ null); } /** Clears mNextVibration if set, ending it cleanly */ @GuardedBy("mLock") - private void clearNextVibrationLocked(Status status, CallerInfo endedBy) { - if (mNextVibration != null) { + private void clearNextSessionLocked(Status status, CallerInfo endedBy) { + if (mNextSession != null) { if (DEBUG) { - Slog.d(TAG, "Dropping pending vibration from " + mNextVibration.getCallerInfo() + Slog.d(TAG, "Dropping pending vibration from " + mNextSession.getCallerInfo() + " with status: " + status); } // Clearing next vibration before playing it, end it and report metrics right away. - endVibrationLocked(mNextVibration, status, endedBy); - mNextVibration = null; + endSessionLocked(mNextSession, status, endedBy); + mNextSession = null; } } /** Clears mCurrentVibration if set, reporting metrics */ @GuardedBy("mLock") - private void clearCurrentVibrationLocked() { - if (mCurrentVibration != null) { - mCurrentVibration.unlinkToDeath(); - logAndRecordVibration(mCurrentVibration.getDebugInfo()); - mCurrentVibration = null; + private void clearCurrentSessionLocked() { + if (mCurrentSession != null) { + mCurrentSession.unlinkToDeath(); + logAndRecordVibration(mCurrentSession.getDebugInfo()); + mCurrentSession = null; mLock.notify(); // Notify if waiting for current vibration to end. } } @GuardedBy("mLock") - private void maybeClearCurrentAndNextVibrationsLocked( + private void maybeClearCurrentAndNextSessionsLocked( Predicate<VibrationSession> shouldEndSessionPredicate, Status endStatus) { // TODO(b/372241975): investigate why external vibrations were not handled here before - if (!(mNextVibration instanceof ExternalVibrationSession) - && shouldEndSessionPredicate.test(mNextVibration)) { - clearNextVibrationLocked(endStatus); + if (!(mNextSession instanceof ExternalVibrationSession) + && shouldEndSessionPredicate.test(mNextSession)) { + clearNextSessionLocked(endStatus); } - if (!(mCurrentVibration instanceof ExternalVibrationSession) - && shouldEndSessionPredicate.test(mCurrentVibration)) { - mCurrentVibration.requestEnd(endStatus); + if (!(mCurrentSession instanceof ExternalVibrationSession) + && shouldEndSessionPredicate.test(mCurrentSession)) { + mCurrentSession.requestEnd(endStatus); } } @@ -1902,12 +2173,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { * * @return true if the vibration completed, or false if waiting timed out. */ - public boolean waitForCurrentVibrationEnd(long maxWaitMillis) { + public boolean waitForCurrentSessionEnd(long maxWaitMillis) { long now = SystemClock.elapsedRealtime(); long deadline = now + maxWaitMillis; synchronized (mLock) { while (true) { - if (mCurrentVibration == null) { + if (mCurrentSession == null) { return true; // Done } if (now >= deadline) { // Note that thread.wait(0) waits indefinitely. @@ -1965,7 +2236,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { synchronized (mLock) { if (!hasExternalControlCapability()) { - endVibrationLocked(session, Status.IGNORED_UNSUPPORTED); + endSessionLocked(session, Status.IGNORED_UNSUPPORTED); return session.getScale(); } @@ -1976,17 +2247,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid() + " tried to play externally controlled vibration" + " without VIBRATE permission, ignoring."); - endVibrationLocked(session, Status.IGNORED_MISSING_PERMISSION); + endSessionLocked(session, Status.IGNORED_MISSING_PERMISSION); return session.getScale(); } Status ignoreStatus = shouldIgnoreVibrationLocked(session.callerInfo); if (ignoreStatus != null) { - endVibrationLocked(session, ignoreStatus); + endSessionLocked(session, ignoreStatus); return session.getScale(); } - if ((mCurrentVibration instanceof ExternalVibrationSession evs) + if ((mCurrentSession instanceof ExternalVibrationSession evs) && evs.isHoldingSameVibration(vib)) { // We are already playing this external vibration, so we can return the same // scale calculated in the previous call to this method. @@ -1994,17 +2265,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } // Check if ongoing vibration is more important than this vibration. - Vibration.EndInfo ignoreInfo = shouldIgnoreVibrationForOngoingLocked(session); + Vibration.EndInfo ignoreInfo = shouldIgnoreForOngoingLocked(session); if (ignoreInfo != null) { - endVibrationLocked(session, ignoreInfo.status, ignoreInfo.endedBy); + endSessionLocked(session, ignoreInfo.status, ignoreInfo.endedBy); return session.getScale(); } // First clear next request, so it won't start when the current one ends. - clearNextVibrationLocked(Status.IGNORED_FOR_EXTERNAL, session.callerInfo); - mNextVibration = session; + clearNextSessionLocked(Status.IGNORED_FOR_EXTERNAL, session.callerInfo); + mNextSession = session; - if (mCurrentVibration != null) { + if (mCurrentSession != null) { // Cancel any vibration that may be playing and ready the vibrator, even if // we have an externally controlled vibration playing already. // Since the interface defines that only one externally controlled @@ -2016,36 +2287,36 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // as we would need to mute the old one still if it came from a different // controller. session.stats.reportInterruptedAnotherVibration( - mCurrentVibration.getCallerInfo()); - mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED, + mCurrentSession.getCallerInfo()); + mCurrentSession.requestEnd(Status.CANCELLED_SUPERSEDED, session.callerInfo, /* immediate= */ true); waitForCompletion = true; } } // Wait for lock and interact with HAL to set external control outside main lock. if (waitForCompletion) { - if (!waitForCurrentVibrationEnd(VIBRATION_CANCEL_WAIT_MILLIS)) { + if (!waitForCurrentSessionEnd(VIBRATION_CANCEL_WAIT_MILLIS)) { Slog.e(TAG, "Timed out waiting for vibration to cancel"); synchronized (mLock) { - if (mNextVibration == session) { - mNextVibration = null; + if (mNextSession == session) { + mNextSession = null; } - endVibrationLocked(session, Status.IGNORED_ERROR_CANCELLING); + endSessionLocked(session, Status.IGNORED_ERROR_CANCELLING); return session.getScale(); } } } synchronized (mLock) { - if (mNextVibration == session) { + if (mNextSession == session) { // This is still the next vibration to be played. - mNextVibration = null; + mNextSession = null; } else { // A new request took the place of this one, maybe with higher importance. // Next vibration already cleared with the right status, just return here. return session.getScale(); } if (!session.linkToDeath()) { - endVibrationLocked(session, Status.IGNORED_ERROR_TOKEN); + endSessionLocked(session, Status.IGNORED_ERROR_TOKEN); return session.getScale(); } if (DEBUG) { @@ -2062,7 +2333,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // should be ignored or scaled. mVibrationSettings.update(); } - mCurrentVibration = session; + mCurrentSession = session; session.scale(mVibrationScaler, attrs.getUsage()); // Vibrator will start receiving data from external channels after this point. @@ -2080,12 +2351,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Trace.traceBegin(TRACE_TAG_VIBRATOR, "onExternalVibrationStop"); try { synchronized (mLock) { - if ((mCurrentVibration instanceof ExternalVibrationSession evs) + if ((mCurrentSession instanceof ExternalVibrationSession evs) && evs.isHoldingSameVibration(vib)) { if (DEBUG) { Slog.d(TAG, "Stopping external vibration: " + vib); } - mCurrentVibration.requestEnd(Status.FINISHED); + mCurrentSession.requestEnd(Status.FINISHED); } } } finally { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 5cff37a36656..10f096c9031b 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -858,10 +858,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo, wallpaper.getDescription()); } else { + WallpaperDescription desc = new WallpaperDescription.Builder().setComponent( + (connection.mInfo != null) ? connection.mInfo.getComponent() + : null).build(); connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false, wpdData.mWidth, wpdData.mHeight, - wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo, - /* description= */ null); + wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo, desc); } } catch (RemoteException e) { Slog.w(TAG, "Failed attaching wallpaper on display", e); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 73ae51c6e64a..14be59f27f84 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -254,6 +254,7 @@ import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.START_TAG; +import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -10308,6 +10309,21 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (pictureInPictureArgs != null && pictureInPictureArgs.hasSourceBoundsHint()) { pictureInPictureArgs.getSourceRectHint().offset(windowBounds.left, windowBounds.top); } + + if (android.app.Flags.enableTvImplicitEnterPipRestriction()) { + PackageManager pm = mAtmService.mContext.getPackageManager(); + if (pictureInPictureArgs.isAutoEnterEnabled() + && pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK) + && pm.checkPermission(Manifest.permission.TV_IMPLICIT_ENTER_PIP, packageName) + == PackageManager.PERMISSION_DENIED) { + Log.i(TAG, + "Auto-enter PiP only allowed on TV if android.permission" + + ".TV_IMPLICIT_ENTER_PIP permission is held by the app."); + PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder(); + builder.setAutoEnterEnabled(false); + pictureInPictureArgs.copyOnlySet(builder.build()); + } + } } private void applyLocaleOverrideIfNeeded(Configuration resolvedConfig) { diff --git a/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp index a47ab9d27c17..46be79e7c097 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp @@ -16,27 +16,32 @@ #define LOG_TAG "VibratorManagerService" -#include <nativehelper/JNIHelp.h> -#include "android_runtime/AndroidRuntime.h" -#include "core_jni_helpers.h" -#include "jni.h" +#include "com_android_server_vibrator_VibratorManagerService.h" +#include <nativehelper/JNIHelp.h> #include <utils/Log.h> #include <utils/misc.h> - #include <vibratorservice/VibratorManagerHalController.h> -#include "com_android_server_vibrator_VibratorManagerService.h" +#include <unordered_map> + +#include "android_runtime/AndroidRuntime.h" +#include "core_jni_helpers.h" +#include "jni.h" namespace android { static JavaVM* sJvm = nullptr; -static jmethodID sMethodIdOnComplete; +static jmethodID sMethodIdOnSyncedVibrationComplete; +static jmethodID sMethodIdOnVibrationSessionComplete; static std::mutex gManagerMutex; static vibrator::ManagerHalController* gManager GUARDED_BY(gManagerMutex) = nullptr; class NativeVibratorManagerService { public: + using IVibrationSession = aidl::android::hardware::vibrator::IVibrationSession; + using VibrationSessionConfig = aidl::android::hardware::vibrator::VibrationSessionConfig; + NativeVibratorManagerService(JNIEnv* env, jobject callbackListener) : mHal(std::make_unique<vibrator::ManagerHalController>()), mCallbackListener(env->NewGlobalRef(callbackListener)) { @@ -52,15 +57,69 @@ public: vibrator::ManagerHalController* hal() const { return mHal.get(); } - std::function<void()> createCallback(jlong vibrationId) { + std::function<void()> createSyncedVibrationCallback(jlong vibrationId) { return [vibrationId, this]() { auto jniEnv = GetOrAttachJNIEnvironment(sJvm); - jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, vibrationId); + jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnSyncedVibrationComplete, + vibrationId); }; } + std::function<void()> createVibrationSessionCallback(jlong sessionId) { + return [sessionId, this]() { + auto jniEnv = GetOrAttachJNIEnvironment(sJvm); + jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnVibrationSessionComplete, + sessionId); + std::lock_guard<std::mutex> lock(mSessionMutex); + auto it = mSessions.find(sessionId); + if (it != mSessions.end()) { + mSessions.erase(it); + } + }; + } + + bool startSession(jlong sessionId, const std::vector<int32_t>& vibratorIds) { + VibrationSessionConfig config; + auto callback = createVibrationSessionCallback(sessionId); + auto result = hal()->startSession(vibratorIds, config, callback); + if (!result.isOk()) { + return false; + } + + std::lock_guard<std::mutex> lock(mSessionMutex); + mSessions[sessionId] = std::move(result.value()); + return true; + } + + void closeSession(jlong sessionId) { + std::lock_guard<std::mutex> lock(mSessionMutex); + auto it = mSessions.find(sessionId); + if (it != mSessions.end()) { + it->second->close(); + // Keep session, it can still be aborted. + } + } + + void abortSession(jlong sessionId) { + std::lock_guard<std::mutex> lock(mSessionMutex); + auto it = mSessions.find(sessionId); + if (it != mSessions.end()) { + it->second->abort(); + mSessions.erase(it); + } + } + + void clearSessions() { + hal()->clearSessions(); + std::lock_guard<std::mutex> lock(mSessionMutex); + mSessions.clear(); + } + private: + std::mutex mSessionMutex; const std::unique_ptr<vibrator::ManagerHalController> mHal; + std::unordered_map<jlong, std::shared_ptr<IVibrationSession>> mSessions + GUARDED_BY(mSessionMutex); const jobject mCallbackListener; }; @@ -142,7 +201,7 @@ static jboolean nativeTriggerSynced(JNIEnv* env, jclass /* clazz */, jlong servi ALOGE("nativeTriggerSynced failed because native service was not initialized"); return JNI_FALSE; } - auto callback = service->createCallback(vibrationId); + auto callback = service->createSyncedVibrationCallback(vibrationId); return service->hal()->triggerSynced(callback).isOk() ? JNI_TRUE : JNI_FALSE; } @@ -156,8 +215,47 @@ static void nativeCancelSynced(JNIEnv* env, jclass /* clazz */, jlong servicePtr service->hal()->cancelSynced(); } +static jboolean nativeStartSession(JNIEnv* env, jclass /* clazz */, jlong servicePtr, + jlong sessionId, jintArray vibratorIds) { + NativeVibratorManagerService* service = + reinterpret_cast<NativeVibratorManagerService*>(servicePtr); + if (service == nullptr) { + ALOGE("nativeStartSession failed because native service was not initialized"); + return JNI_FALSE; + } + jsize size = env->GetArrayLength(vibratorIds); + std::vector<int32_t> ids(size); + env->GetIntArrayRegion(vibratorIds, 0, size, reinterpret_cast<jint*>(ids.data())); + return service->startSession(sessionId, ids) ? JNI_TRUE : JNI_FALSE; +} + +static void nativeEndSession(JNIEnv* env, jclass /* clazz */, jlong servicePtr, jlong sessionId, + jboolean shouldAbort) { + NativeVibratorManagerService* service = + reinterpret_cast<NativeVibratorManagerService*>(servicePtr); + if (service == nullptr) { + ALOGE("nativeEndSession failed because native service was not initialized"); + return; + } + if (shouldAbort) { + service->abortSession(sessionId); + } else { + service->closeSession(sessionId); + } +} + +static void nativeClearSessions(JNIEnv* env, jclass /* clazz */, jlong servicePtr) { + NativeVibratorManagerService* service = + reinterpret_cast<NativeVibratorManagerService*>(servicePtr); + if (service == nullptr) { + ALOGE("nativeClearSessions failed because native service was not initialized"); + return; + } + service->clearSessions(); +} + inline static constexpr auto sNativeInitMethodSignature = - "(Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;)J"; + "(Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;)J"; static const JNINativeMethod method_table[] = { {"nativeInit", sNativeInitMethodSignature, (void*)nativeInit}, @@ -167,15 +265,20 @@ static const JNINativeMethod method_table[] = { {"nativePrepareSynced", "(J[I)Z", (void*)nativePrepareSynced}, {"nativeTriggerSynced", "(JJ)Z", (void*)nativeTriggerSynced}, {"nativeCancelSynced", "(J)V", (void*)nativeCancelSynced}, + {"nativeStartSession", "(JJ[I)Z", (void*)nativeStartSession}, + {"nativeEndSession", "(JJZ)V", (void*)nativeEndSession}, + {"nativeClearSessions", "(J)V", (void*)nativeClearSessions}, }; int register_android_server_vibrator_VibratorManagerService(JavaVM* jvm, JNIEnv* env) { sJvm = jvm; auto listenerClassName = - "com/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener"; + "com/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks"; jclass listenerClass = FindClassOrDie(env, listenerClassName); - sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(J)V"); - + sMethodIdOnSyncedVibrationComplete = + GetMethodIDOrDie(env, listenerClass, "onSyncedVibrationComplete", "(J)V"); + sMethodIdOnVibrationSessionComplete = + GetMethodIDOrDie(env, listenerClass, "onVibrationSessionComplete", "(J)V"); return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorManagerService", method_table, NELEM(method_table)); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3805c02d1bb9..9759772ae8bd 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -119,7 +119,6 @@ import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockSettingsInternal; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accounts.AccountManagerService; -import com.android.server.adaptiveauth.AdaptiveAuthService; import com.android.server.adb.AdbService; import com.android.server.alarm.AlarmManagerService; import com.android.server.am.ActivityManagerService; @@ -205,6 +204,7 @@ import com.android.server.os.BugreportManagerService; import com.android.server.os.DeviceIdentifiersPolicyService; import com.android.server.os.NativeTombstoneManagerService; import com.android.server.os.SchedulingPolicyService; +import com.android.server.os.instrumentation.DynamicInstrumentationManagerService; import com.android.server.pdb.PersistentDataBlockService; import com.android.server.people.PeopleService; import com.android.server.permission.access.AccessCheckingService; @@ -249,6 +249,7 @@ import com.android.server.security.AttestationVerificationManagerService; import com.android.server.security.FileIntegrityService; import com.android.server.security.KeyAttestationApplicationIdProviderService; import com.android.server.security.KeyChainSystemService; +import com.android.server.security.adaptiveauthentication.AdaptiveAuthenticationService; import com.android.server.security.advancedprotection.AdvancedProtectionService; import com.android.server.security.rkp.RemoteProvisioningService; import com.android.server.selinux.SelinuxAuditLogsService; @@ -2650,8 +2651,8 @@ public final class SystemServer implements Dumpable { t.traceEnd(); if (android.adaptiveauth.Flags.enableAdaptiveAuth()) { - t.traceBegin("StartAdaptiveAuthService"); - mSystemServiceManager.startService(AdaptiveAuthService.class); + t.traceBegin("StartAdaptiveAuthenticationService"); + mSystemServiceManager.startService(AdaptiveAuthenticationService.class); t.traceEnd(); } @@ -2890,6 +2891,13 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(TracingServiceProxy.class); t.traceEnd(); + // UprobeStats DynamicInstrumentationManager + if (com.android.art.flags.Flags.executableMethodFileOffsets()) { + t.traceBegin("StartDynamicInstrumentationManager"); + mSystemServiceManager.startService(DynamicInstrumentationManagerService.class); + t.traceEnd(); + } + // It is now time to start up the app processes... t.traceBegin("MakeLockSettingsServiceReady"); diff --git a/services/proguard.flags b/services/proguard.flags index cdd41abf6c7c..977bd19a7236 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -82,7 +82,7 @@ -keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaJackDetector { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaMidiDevice { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorController$OnVibrationCompleteListener { *; } --keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorManagerService$OnSyncedVibrationCompleteListener { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorManagerService$VibratorManagerNativeCallbacks { *; } -keepclasseswithmembers,allowoptimization,allowaccessmodification class com.android.server.** { *** *FromNative(...); } diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp new file mode 100644 index 000000000000..2c2e5fdb68d9 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp @@ -0,0 +1,44 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], + default_team: "trendy_team_system_performance", +} + +android_test { + name: "DynamicInstrumentationManagerServiceTests", + srcs: ["src/**/*.java"], + + static_libs: [ + "androidx.test.core", + "androidx.test.runner", + "hamcrest-library", + "platform-test-annotations", + "services.core", + "testables", + "truth", + ], + + certificate: "platform", + platform_apis: true, + test_suites: [ + "device-tests", + ], +} diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml b/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml new file mode 100644 index 000000000000..4913d706a72d --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.os.instrumentation" > + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.os.instrumentation" + android:label="DynamicInstrumentationmanagerService Unit Tests"/> +</manifest>
\ No newline at end of file diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING b/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING new file mode 100644 index 000000000000..33defed0eae6 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "DynamicInstrumentationManagerServiceTests" + } + ] +} diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java new file mode 100644 index 000000000000..04073fab2059 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +public abstract class TestAbstractClass { + abstract void abstractMethod(); + + void concreteMethod() { + } +} diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java new file mode 100644 index 000000000000..2c25e7a52f73 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +public class TestAbstractClassImpl extends TestAbstractClass { + @Override + void abstractMethod() { + } +} diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java new file mode 100644 index 000000000000..085f5953f0e5 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java @@ -0,0 +1,40 @@ +/* + * 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; + +public class TestClass { + void primitiveParams(boolean a, boolean[] b, byte c, byte[] d, char e, char[] f, short g, + short[] h, int i, int[] j, long k, long[] l, float m, float[] n, double o, double[] p) { + } + + void classParams(String a, String[] b) { + } + + private void privateMethod() { + } + + /** + * docs! + */ + public void publicMethod() { + } + + private static class InnerClass { + private void innerMethod(InnerClass arg, InnerClass[] argArray) { + } + } +} diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java new file mode 100644 index 000000000000..7af4f254ab1c --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +/** + * docs! + */ +public interface TestInterface { + /** + * docs! + */ + void interfaceMethod(); + + /** + * docs! + */ + default void defaultMethod() { + } +} diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java new file mode 100644 index 000000000000..53aecbc08939 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +public class TestInterfaceImpl implements TestInterface { + @Override + public void interfaceMethod() { + } +} 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 new file mode 100644 index 000000000000..5492ba6b9dd1 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java @@ -0,0 +1,143 @@ +/* + * 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.os.instrumentation; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; + +import android.os.instrumentation.MethodDescriptor; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.TestAbstractClass; +import com.android.server.TestAbstractClassImpl; +import com.android.server.TestClass; +import com.android.server.TestInterface; +import com.android.server.TestInterfaceImpl; + +import org.junit.Test; + +import java.lang.reflect.Method; + + +/** + * Test class for + * {@link DynamicInstrumentationManagerService#parseMethodDescriptor(ClassLoader, + * MethodDescriptor)}. + * <p> + * Build/Install/Run: + * atest FrameworksMockingServicesTests:ParseMethodDescriptorTest + */ +@Presubmit +@SmallTest +public class ParseMethodDescriptorTest { + private static final String[] PRIMITIVE_PARAMS = new String[]{ + "boolean", "boolean[]", "byte", "byte[]", "char", "char[]", "short", "short[]", "int", + "int[]", "long", "long[]", "float", "float[]", "double", "double[]"}; + private static final String[] CLASS_PARAMS = + new String[]{"java.lang.String", "java.lang.String[]"}; + + @Test + public void primitiveParams() { + assertNotNull(parseMethodDescriptor(TestClass.class.getName(), "primitiveParams", + PRIMITIVE_PARAMS)); + } + + @Test + public void classParams() { + assertNotNull( + parseMethodDescriptor(TestClass.class.getName(), "classParams", CLASS_PARAMS)); + } + + @Test + public void publicMethod() { + assertNotNull( + parseMethodDescriptor(TestClass.class.getName(), "publicMethod")); + } + + @Test + public void privateMethod() { + assertNotNull( + parseMethodDescriptor(TestClass.class.getName(), "privateMethod")); + } + + @Test + public void innerClass() { + assertNotNull( + parseMethodDescriptor(TestClass.class.getName() + "$InnerClass", "innerMethod", + new String[]{TestClass.class.getName() + "$InnerClass", + TestClass.class.getName() + "$InnerClass[]"})); + } + + @Test + public void interface_concreteMethod() { + assertNotNull( + parseMethodDescriptor(TestInterfaceImpl.class.getName(), "interfaceMethod")); + } + + @Test + public void interface_defaultMethod() { + assertNotNull( + parseMethodDescriptor(TestInterface.class.getName(), "defaultMethod")); + } + + @Test + public void abstractClassImpl_abstractMethod() { + assertNotNull( + parseMethodDescriptor(TestAbstractClassImpl.class.getName(), "abstractMethod")); + } + + @Test + public void abstractClass_concreteMethod() { + assertNotNull( + parseMethodDescriptor(TestAbstractClass.class.getName(), "concreteMethod")); + } + + @Test + public void notFound_illegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> parseMethodDescriptor("foo", "bar")); + assertThrows(IllegalArgumentException.class, + () -> parseMethodDescriptor(TestClass.class.getName(), "bar")); + assertThrows(IllegalArgumentException.class, + () -> parseMethodDescriptor(TestClass.class.getName(), "primitiveParams", + new String[]{"int"})); + } + + private Method parseMethodDescriptor(String fqcn, String methodName) { + return DynamicInstrumentationManagerService.parseMethodDescriptor( + getClass().getClassLoader(), + getMethodDescriptor(fqcn, methodName, new String[]{})); + } + + private Method parseMethodDescriptor(String fqcn, String methodName, String[] fqParameters) { + return DynamicInstrumentationManagerService.parseMethodDescriptor( + getClass().getClassLoader(), + getMethodDescriptor(fqcn, methodName, fqParameters)); + } + + private MethodDescriptor getMethodDescriptor(String fqcn, String methodName, + String[] fqParameters) { + MethodDescriptor methodDescriptor = new MethodDescriptor(); + methodDescriptor.fullyQualifiedClassName = fqcn; + methodDescriptor.methodName = methodName; + methodDescriptor.fullyQualifiedParameters = fqParameters; + return methodDescriptor; + } + + +} diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml index 37a34eeb9724..205ff058275a 100644 --- a/services/tests/displayservicetests/AndroidManifest.xml +++ b/services/tests/displayservicetests/AndroidManifest.xml @@ -29,7 +29,6 @@ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.MANAGE_USB" /> - <uses-permission android:name="android.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE" /> <!-- Permissions needed for DisplayTransformManagerTest --> <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java index 06f1b27410ff..a8708f9f9e6e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java @@ -223,7 +223,8 @@ public class BrightnessSynchronizerTest { mIntRangeUserPerceptionEnabled); mSynchronizer.startSynchronizing(); verify(mDisplayManagerMock).registerDisplayListener(mDisplayListenerCaptor.capture(), - isA(Handler.class), eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + isA(Handler.class), eq(0L), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); mDisplayListener = mDisplayListenerCaptor.getValue(); verify(mContentResolverSpy).registerContentObserver(eq(BRIGHTNESS_URI), eq(false), diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index b917af4d796e..80e5ee39c13d 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -19,13 +19,16 @@ package com.android.server.display; import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY; import static android.Manifest.permission.ADD_TRUSTED_DISPLAY; import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT; +import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS; import static android.Manifest.permission.MANAGE_DISPLAYS; +import static android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; +import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY; import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; @@ -96,6 +99,7 @@ import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayOffloader; +import android.hardware.display.DisplayTopology; import android.hardware.display.DisplayViewport; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; @@ -111,11 +115,13 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.MessageQueue; +import android.os.PermissionEnforcer; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserManager; +import android.os.test.FakePermissionEnforcer; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; @@ -203,11 +209,13 @@ public class DisplayManagerServiceTest { private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display"; private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests"; - private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; + private static final long STANDARD_DISPLAY_EVENTS = + DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS = - STANDARD_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED; + STANDARD_DISPLAY_EVENTS + | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED; private static final String EVENT_DISPLAY_ADDED = "EVENT_DISPLAY_ADDED"; private static final String EVENT_DISPLAY_REMOVED = "EVENT_DISPLAY_REMOVED"; @@ -249,6 +257,8 @@ public class DisplayManagerServiceTest { private int[] mAllowedHdrOutputTypes; + private final FakePermissionEnforcer mPermissionEnforcer = new FakePermissionEnforcer(); + private final DisplayManagerService.Injector mShortMockedInjector = new DisplayManagerService.Injector() { @Override @@ -426,6 +436,13 @@ public class DisplayManagerServiceTest { when(mContext.getResources()).thenReturn(mResources); mUserManager = Mockito.spy(mContext.getSystemService(UserManager.class)); + mPermissionEnforcer.grant(CONTROL_DISPLAY_BRIGHTNESS); + mPermissionEnforcer.grant(MODIFY_USER_PREFERRED_DISPLAY_MODE); + doReturn(Context.PERMISSION_ENFORCER_SERVICE).when(mContext).getSystemServiceName( + eq(PermissionEnforcer.class)); + doReturn(mPermissionEnforcer).when(mContext).getSystemService( + eq(Context.PERMISSION_ENFORCER_SERVICE)); + VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext); when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm); when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); @@ -2379,7 +2396,7 @@ public class DisplayManagerServiceTest { // register display listener callback FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(); long allEventsExceptDisplayAdded = STANDARD_DISPLAY_EVENTS - & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED; + & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED; displayManagerBinderService.registerCallbackWithEventMask(callback, allEventsExceptDisplayAdded); @@ -2450,7 +2467,7 @@ public class DisplayManagerServiceTest { FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(); long allEventsExceptDisplayRemoved = STANDARD_DISPLAY_EVENTS - & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; + & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; displayManagerBinderService.registerCallbackWithEventMask(callback, allEventsExceptDisplayRemoved); @@ -3665,6 +3682,87 @@ public class DisplayManagerServiceTest { verify(mMockVirtualDisplayAdapter).releaseVirtualDisplayLocked(binder, callingUid); } + @Test + public void testGetDisplayTopology() { + Settings.Global.putInt(mContext.getContentResolver(), + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 1); + manageDisplaysPermission(/* granted= */ true); + when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + registerDefaultDisplays(displayManager); + initDisplayPowerController(localService); + + DisplayTopology topology = displayManagerBinderService.getDisplayTopology(); + assertNotNull(topology); + DisplayTopology.TreeNode display = topology.getRoot(); + assertNotNull(display); + assertEquals(Display.DEFAULT_DISPLAY, display.getDisplayId()); + } + + @Test + public void testGetDisplayTopology_NullIfFlagDisabled() { + manageDisplaysPermission(/* granted= */ true); + when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(false); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + registerDefaultDisplays(displayManager); + initDisplayPowerController(localService); + + DisplayTopology topology = displayManagerBinderService.getDisplayTopology(); + assertNull(topology); + } + + @Test + public void testGetDisplayTopology_withoutPermission_shouldThrowException() { + when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + registerDefaultDisplays(displayManager); + initDisplayPowerController(localService); + + assertThrows(SecurityException.class, displayManagerBinderService::getDisplayTopology); + } + + @Test + public void testSetDisplayTopology() { + manageDisplaysPermission(/* granted= */ true); + when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + registerDefaultDisplays(displayManager); + initDisplayPowerController(localService); + + displayManagerBinderService.setDisplayTopology(new DisplayTopology()); + } + + @Test + public void testSetDisplayTopology_withoutPermission_shouldThrowException() { + when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + registerDefaultDisplays(displayManager); + initDisplayPowerController(localService); + + assertThrows(SecurityException.class, + () -> displayManagerBinderService.setDisplayTopology(new DisplayTopology())); + } + private void initDisplayPowerController(DisplayManagerInternal localService) { localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() { @Override @@ -3848,6 +3946,10 @@ public class DisplayManagerServiceTest { DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo(); displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked()); displayDeviceInfo.modeId = modeId; + if (modeId > 0 && modeId <= displayDeviceInfo.supportedModes.length) { + displayDeviceInfo.renderFrameRate = + displayDeviceInfo.supportedModes[modeId - 1].getRefreshRate(); + } updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo); } @@ -4015,9 +4117,11 @@ public class DisplayManagerServiceTest { private void manageDisplaysPermission(boolean granted) { if (granted) { doNothing().when(mContext).enforceCallingOrSelfPermission(eq(MANAGE_DISPLAYS), any()); + mPermissionEnforcer.grant(MANAGE_DISPLAYS); } else { doThrow(new SecurityException("MANAGE_DISPLAYS permission denied")).when(mContext) .enforceCallingOrSelfPermission(eq(MANAGE_DISPLAYS), any()); + mPermissionEnforcer.revoke(MANAGE_DISPLAYS); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 27fd1e6187bb..e64d9855bc54 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -139,6 +139,7 @@ public final class DisplayPowerControllerTest { private TestLooper mTestLooper; private Handler mHandler; private DisplayPowerControllerHolder mHolder; + private DisplayBrightnessState mDisplayBrightnessState; private Sensor mProxSensor; @Mock @@ -187,6 +188,7 @@ public final class DisplayPowerControllerTest { mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); mHandler = new Handler(mTestLooper.getLooper()); + mDisplayBrightnessState = DisplayBrightnessState.builder().build(); // Set some settings to minimize unexpected events and have a consistent starting state Settings.System.putInt(mContext.getContentResolver(), @@ -1453,10 +1455,11 @@ public final class DisplayPowerControllerTest { when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f); when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f); - when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer( - invocation -> DisplayBrightnessState.builder() - .setIsSlowChange(invocation.getArgument(2)) - .setBrightness(invocation.getArgument(1)) + when(mHolder.clamperController.clamp(any(), any(), anyFloat(), + anyBoolean(), anyInt())).thenAnswer( + invocation -> DisplayBrightnessState.Builder.from(mDisplayBrightnessState) + .setIsSlowChange(invocation.getArgument(3)) + .setBrightness(invocation.getArgument(2)) .setMaxBrightness(PowerManager.BRIGHTNESS_MAX) .setCustomAnimationRate(transitionRate).build()); @@ -1477,10 +1480,11 @@ public final class DisplayPowerControllerTest { when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f); when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f); - when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer( - invocation -> DisplayBrightnessState.builder() - .setIsSlowChange(invocation.getArgument(2)) - .setBrightness(invocation.getArgument(1)) + when(mHolder.clamperController.clamp(any(), any(), anyFloat(), + anyBoolean(), anyInt())).thenAnswer( + invocation -> DisplayBrightnessState.Builder.from(mDisplayBrightnessState) + .setIsSlowChange(invocation.getArgument(3)) + .setBrightness(invocation.getArgument(2)) .setMaxBrightness(PowerManager.BRIGHTNESS_MAX) .setCustomAnimationRate(transitionRate).build()); @@ -2574,10 +2578,11 @@ public final class DisplayPowerControllerTest { BrightnessClamperController clamperController = mock(BrightnessClamperController.class); when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX); - when(clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer( - invocation -> DisplayBrightnessState.builder() - .setIsSlowChange(invocation.getArgument(2)) - .setBrightness(invocation.getArgument(1)) + when(clamperController.clamp(any(), any(), anyFloat(), anyBoolean(), + anyInt())).thenAnswer( + invocation -> DisplayBrightnessState.Builder.from(mDisplayBrightnessState) + .setIsSlowChange(invocation.getArgument(3)) + .setBrightness(invocation.getArgument(2)) .setMaxBrightness(PowerManager.BRIGHTNESS_MAX) .setCustomAnimationRate(-1).build()); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt index 85e73561cf59..a2d2a81b20b4 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt @@ -16,6 +16,7 @@ package com.android.server.display +import android.hardware.display.DisplayTopology import android.util.DisplayMetrics import android.view.Display import android.view.DisplayInfo diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt deleted file mode 100644 index cd8c26d0d337..000000000000 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt +++ /dev/null @@ -1,476 +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.display - -import android.view.Display -import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_BOTTOM -import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_TOP -import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_RIGHT -import com.google.common.truth.Truth.assertThat -import org.junit.Test - -class DisplayTopologyTest { - private val topology = DisplayTopology() - - @Test - fun addOneDisplay() { - val displayId = 1 - val width = 800f - val height = 600f - - topology.addDisplay(displayId, width, height) - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId) - - val display = topology.mRoot!! - assertThat(display.mDisplayId).isEqualTo(displayId) - assertThat(display.mWidth).isEqualTo(width) - assertThat(display.mHeight).isEqualTo(height) - assertThat(display.mChildren).isEmpty() - } - - @Test - fun addTwoDisplays() { - val displayId1 = 1 - val width1 = 800f - val height1 = 600f - - val displayId2 = 2 - val width2 = 1000f - val height2 = 1500f - - topology.addDisplay(displayId1, width1, height1) - topology.addDisplay(displayId2, width2, height2) - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1) - - val display1 = topology.mRoot!! - assertThat(display1.mDisplayId).isEqualTo(displayId1) - assertThat(display1.mWidth).isEqualTo(width1) - assertThat(display1.mHeight).isEqualTo(height1) - assertThat(display1.mChildren).hasSize(1) - - val display2 = display1.mChildren[0] - assertThat(display2.mDisplayId).isEqualTo(displayId2) - assertThat(display2.mWidth).isEqualTo(width2) - assertThat(display2.mHeight).isEqualTo(height2) - assertThat(display2.mChildren).isEmpty() - assertThat(display2.mPosition).isEqualTo(POSITION_TOP) - assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2) - } - - @Test - fun addManyDisplays() { - val displayId1 = 1 - val width1 = 800f - val height1 = 600f - - val displayId2 = 2 - val width2 = 1000f - val height2 = 1500f - - topology.addDisplay(displayId1, width1, height1) - topology.addDisplay(displayId2, width2, height2) - - val noOfDisplays = 30 - for (i in 3..noOfDisplays) { - topology.addDisplay(/* displayId= */ i, width1, height1) - } - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1) - - val display1 = topology.mRoot!! - assertThat(display1.mDisplayId).isEqualTo(displayId1) - assertThat(display1.mWidth).isEqualTo(width1) - assertThat(display1.mHeight).isEqualTo(height1) - assertThat(display1.mChildren).hasSize(1) - - val display2 = display1.mChildren[0] - assertThat(display2.mDisplayId).isEqualTo(displayId2) - assertThat(display2.mWidth).isEqualTo(width2) - assertThat(display2.mHeight).isEqualTo(height2) - assertThat(display2.mChildren).hasSize(1) - assertThat(display2.mPosition).isEqualTo(POSITION_TOP) - assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2) - - var display = display2 - for (i in 3..noOfDisplays) { - display = display.mChildren[0] - assertThat(display.mDisplayId).isEqualTo(i) - assertThat(display.mWidth).isEqualTo(width1) - assertThat(display.mHeight).isEqualTo(height1) - // The last display should have no children - assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0) - assertThat(display.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(display.mOffset).isEqualTo(0) - } - } - - @Test - fun removeDisplays() { - val displayId1 = 1 - val width1 = 800f - val height1 = 600f - - val displayId2 = 2 - val width2 = 1000f - val height2 = 1500f - - topology.addDisplay(displayId1, width1, height1) - topology.addDisplay(displayId2, width2, height2) - - val noOfDisplays = 30 - for (i in 3..noOfDisplays) { - topology.addDisplay(/* displayId= */ i, width1, height1) - } - - var removedDisplays = arrayOf(20) - topology.removeDisplay(20) - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1) - - var display1 = topology.mRoot!! - assertThat(display1.mDisplayId).isEqualTo(displayId1) - assertThat(display1.mWidth).isEqualTo(width1) - assertThat(display1.mHeight).isEqualTo(height1) - assertThat(display1.mChildren).hasSize(1) - - var display2 = display1.mChildren[0] - assertThat(display2.mDisplayId).isEqualTo(displayId2) - assertThat(display2.mWidth).isEqualTo(width2) - assertThat(display2.mHeight).isEqualTo(height2) - assertThat(display2.mChildren).hasSize(1) - assertThat(display2.mPosition).isEqualTo(POSITION_TOP) - assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2) - - var display = display2 - for (i in 3..noOfDisplays) { - if (i in removedDisplays) { - continue - } - display = display.mChildren[0] - assertThat(display.mDisplayId).isEqualTo(i) - assertThat(display.mWidth).isEqualTo(width1) - assertThat(display.mHeight).isEqualTo(height1) - // The last display should have no children - assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0) - assertThat(display.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(display.mOffset).isEqualTo(0) - } - - topology.removeDisplay(22) - removedDisplays += 22 - topology.removeDisplay(23) - removedDisplays += 23 - topology.removeDisplay(25) - removedDisplays += 25 - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1) - - display1 = topology.mRoot!! - assertThat(display1.mDisplayId).isEqualTo(displayId1) - assertThat(display1.mWidth).isEqualTo(width1) - assertThat(display1.mHeight).isEqualTo(height1) - assertThat(display1.mChildren).hasSize(1) - - display2 = display1.mChildren[0] - assertThat(display2.mDisplayId).isEqualTo(displayId2) - assertThat(display2.mWidth).isEqualTo(width2) - assertThat(display2.mHeight).isEqualTo(height2) - assertThat(display2.mChildren).hasSize(1) - assertThat(display2.mPosition).isEqualTo(POSITION_TOP) - assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2) - - display = display2 - for (i in 3..noOfDisplays) { - if (i in removedDisplays) { - continue - } - display = display.mChildren[0] - assertThat(display.mDisplayId).isEqualTo(i) - assertThat(display.mWidth).isEqualTo(width1) - assertThat(display.mHeight).isEqualTo(height1) - // The last display should have no children - assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0) - assertThat(display.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(display.mOffset).isEqualTo(0) - } - } - - @Test - fun removeAllDisplays() { - val displayId = 1 - val width = 800f - val height = 600f - - topology.addDisplay(displayId, width, height) - topology.removeDisplay(displayId) - - assertThat(topology.mPrimaryDisplayId).isEqualTo(Display.INVALID_DISPLAY) - assertThat(topology.mRoot).isNull() - } - - @Test - fun removeDisplayThatDoesNotExist() { - val displayId = 1 - val width = 800f - val height = 600f - - topology.addDisplay(displayId, width, height) - topology.removeDisplay(3) - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId) - - val display = topology.mRoot!! - assertThat(display.mDisplayId).isEqualTo(displayId) - assertThat(display.mWidth).isEqualTo(width) - assertThat(display.mHeight).isEqualTo(height) - assertThat(display.mChildren).isEmpty() - } - - @Test - fun removePrimaryDisplay() { - val displayId1 = 1 - val displayId2 = 2 - val width = 800f - val height = 600f - - topology.addDisplay(displayId1, width, height) - topology.addDisplay(displayId2, width, height) - topology.mPrimaryDisplayId = displayId2 - topology.removeDisplay(displayId2) - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1) - val display = topology.mRoot!! - assertThat(display.mDisplayId).isEqualTo(displayId1) - assertThat(display.mWidth).isEqualTo(width) - assertThat(display.mHeight).isEqualTo(height) - assertThat(display.mChildren).isEmpty() - } - - @Test - fun normalization_noOverlaps_leavesTopologyUnchanged() { - val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, - /* height= */ 600f, /* position= */ null, /* offset= */ 0f) - topology.mRoot = display1 - - val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f, - /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f) - display1.mChildren.add(display2) - - val primaryDisplayId = 3 - val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, - /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f) - display1.mChildren.add(display3) - topology.mPrimaryDisplayId = primaryDisplayId - - val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f, - /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) - display2.mChildren.add(display4) - - topology.normalize() - - assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId) - - val actualDisplay1 = topology.mRoot!! - assertThat(actualDisplay1.mDisplayId).isEqualTo(1) - assertThat(actualDisplay1.mWidth).isEqualTo(200f) - assertThat(actualDisplay1.mHeight).isEqualTo(600f) - assertThat(actualDisplay1.mChildren).hasSize(2) - - val actualDisplay2 = actualDisplay1.mChildren[0] - assertThat(actualDisplay2.mDisplayId).isEqualTo(2) - assertThat(actualDisplay2.mWidth).isEqualTo(600f) - assertThat(actualDisplay2.mHeight).isEqualTo(200f) - assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay2.mOffset).isEqualTo(0f) - assertThat(actualDisplay2.mChildren).hasSize(1) - - val actualDisplay3 = actualDisplay1.mChildren[1] - assertThat(actualDisplay3.mDisplayId).isEqualTo(3) - assertThat(actualDisplay3.mWidth).isEqualTo(600f) - assertThat(actualDisplay3.mHeight).isEqualTo(200f) - assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay3.mOffset).isEqualTo(400f) - assertThat(actualDisplay3.mChildren).isEmpty() - - val actualDisplay4 = actualDisplay2.mChildren[0] - assertThat(actualDisplay4.mDisplayId).isEqualTo(4) - assertThat(actualDisplay4.mWidth).isEqualTo(200f) - assertThat(actualDisplay4.mHeight).isEqualTo(600f) - assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay4.mOffset).isEqualTo(0f) - assertThat(actualDisplay4.mChildren).isEmpty() - } - - @Test - fun normalization_moveDisplayWithoutReparenting() { - val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, - /* height= */ 600f, /* position= */ null, /* offset= */ 0f) - topology.mRoot = display1 - - val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f, - /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) - display1.mChildren.add(display2) - - val primaryDisplayId = 3 - val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, - /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f) - display1.mChildren.add(display3) - topology.mPrimaryDisplayId = primaryDisplayId - - val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f, - /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) - display2.mChildren.add(display4) - - // Display 3 becomes a child of display 2. Display 4 gets moved without changing its parent. - topology.normalize() - - assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId) - - val actualDisplay1 = topology.mRoot!! - assertThat(actualDisplay1.mDisplayId).isEqualTo(1) - assertThat(actualDisplay1.mWidth).isEqualTo(200f) - assertThat(actualDisplay1.mHeight).isEqualTo(600f) - assertThat(actualDisplay1.mChildren).hasSize(1) - - val actualDisplay2 = actualDisplay1.mChildren[0] - assertThat(actualDisplay2.mDisplayId).isEqualTo(2) - assertThat(actualDisplay2.mWidth).isEqualTo(200f) - assertThat(actualDisplay2.mHeight).isEqualTo(600f) - assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay2.mOffset).isEqualTo(0f) - assertThat(actualDisplay2.mChildren).hasSize(2) - - val actualDisplay3 = actualDisplay2.mChildren[1] - assertThat(actualDisplay3.mDisplayId).isEqualTo(3) - assertThat(actualDisplay3.mWidth).isEqualTo(600f) - assertThat(actualDisplay3.mHeight).isEqualTo(200f) - assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay3.mOffset).isEqualTo(10f) - assertThat(actualDisplay3.mChildren).isEmpty() - - val actualDisplay4 = actualDisplay2.mChildren[0] - assertThat(actualDisplay4.mDisplayId).isEqualTo(4) - assertThat(actualDisplay4.mWidth).isEqualTo(200f) - assertThat(actualDisplay4.mHeight).isEqualTo(600f) - assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay4.mOffset).isEqualTo(210f) - assertThat(actualDisplay4.mChildren).isEmpty() - } - - @Test - fun normalization_moveDisplayWithoutReparenting_offsetOutOfBounds() { - val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, - /* height= */ 50f, /* position= */ null, /* offset= */ 0f) - topology.mRoot = display1 - - val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f, - /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f) - display1.mChildren.add(display2) - - val primaryDisplayId = 3 - val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, - /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f) - display1.mChildren.add(display3) - topology.mPrimaryDisplayId = primaryDisplayId - - // Display 3 gets moved and its left side is still on the same line as the right side - // of Display 1, but it no longer touches it (the offset is out of bounds), so Display 2 - // becomes its new parent. - topology.normalize() - - assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId) - - val actualDisplay1 = topology.mRoot!! - assertThat(actualDisplay1.mDisplayId).isEqualTo(1) - assertThat(actualDisplay1.mWidth).isEqualTo(200f) - assertThat(actualDisplay1.mHeight).isEqualTo(50f) - assertThat(actualDisplay1.mChildren).hasSize(1) - - val actualDisplay2 = actualDisplay1.mChildren[0] - assertThat(actualDisplay2.mDisplayId).isEqualTo(2) - assertThat(actualDisplay2.mWidth).isEqualTo(600f) - assertThat(actualDisplay2.mHeight).isEqualTo(200f) - assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay2.mOffset).isEqualTo(0f) - assertThat(actualDisplay2.mChildren).hasSize(1) - - val actualDisplay3 = actualDisplay2.mChildren[0] - assertThat(actualDisplay3.mDisplayId).isEqualTo(3) - assertThat(actualDisplay3.mWidth).isEqualTo(600f) - assertThat(actualDisplay3.mHeight).isEqualTo(200f) - assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_BOTTOM) - assertThat(actualDisplay3.mOffset).isEqualTo(0f) - assertThat(actualDisplay3.mChildren).isEmpty() - } - - @Test - fun normalization_moveAndReparentDisplay() { - val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, - /* height= */ 600f, /* position= */ null, /* offset= */ 0f) - topology.mRoot = display1 - - val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f, - /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) - display1.mChildren.add(display2) - - val primaryDisplayId = 3 - val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, - /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f) - display1.mChildren.add(display3) - topology.mPrimaryDisplayId = primaryDisplayId - - val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f, - /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) - display2.mChildren.add(display4) - - topology.normalize() - - assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId) - - val actualDisplay1 = topology.mRoot!! - assertThat(actualDisplay1.mDisplayId).isEqualTo(1) - assertThat(actualDisplay1.mWidth).isEqualTo(200f) - assertThat(actualDisplay1.mHeight).isEqualTo(600f) - assertThat(actualDisplay1.mChildren).hasSize(1) - - val actualDisplay2 = actualDisplay1.mChildren[0] - assertThat(actualDisplay2.mDisplayId).isEqualTo(2) - assertThat(actualDisplay2.mWidth).isEqualTo(200f) - assertThat(actualDisplay2.mHeight).isEqualTo(600f) - assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay2.mOffset).isEqualTo(0f) - assertThat(actualDisplay2.mChildren).hasSize(1) - - val actualDisplay3 = actualDisplay2.mChildren[0] - assertThat(actualDisplay3.mDisplayId).isEqualTo(3) - assertThat(actualDisplay3.mWidth).isEqualTo(600f) - assertThat(actualDisplay3.mHeight).isEqualTo(200f) - assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay3.mOffset).isEqualTo(400f) - assertThat(actualDisplay3.mChildren).hasSize(1) - - val actualDisplay4 = actualDisplay3.mChildren[0] - assertThat(actualDisplay4.mDisplayId).isEqualTo(4) - assertThat(actualDisplay4.mWidth).isEqualTo(200f) - assertThat(actualDisplay4.mHeight).isEqualTo(600f) - assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay4.mOffset).isEqualTo(-400f) - assertThat(actualDisplay4.mChildren).isEmpty() - } -}
\ No newline at end of file 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 da79f301ee3c..2aafdfa8a4d3 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 @@ -98,6 +98,7 @@ public class BrightnessClamperControllerTest { @Mock private DeviceConfig.Properties mMockProperties; private BrightnessClamperController mClamperController; + private DisplayBrightnessState mDisplayBrightnessState; private TestInjector mTestInjector; @Before @@ -109,6 +110,7 @@ public class BrightnessClamperControllerTest { when(mMockDisplayDeviceData.getAmbientLightSensor()).thenReturn(mMockSensorData); mClamperController = createBrightnessClamperController(); + mDisplayBrightnessState = DisplayBrightnessState.builder().build(); } @Test @@ -192,7 +194,8 @@ public class BrightnessClamperControllerTest { public void testClamp_AppliesModifier() { float initialBrightness = 0.2f; boolean initialSlowChange = true; - mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_ON); + mClamperController.clamp(mDisplayBrightnessState, mMockRequest, initialBrightness, + initialSlowChange, STATE_ON); verify(mMockModifier).apply(eq(mMockRequest), any()); verify(mMockDisplayListenerModifier).apply(eq(mMockRequest), any()); @@ -204,7 +207,8 @@ public class BrightnessClamperControllerTest { float initialBrightness = 0.2f; boolean initialSlowChange = true; when(mMockModifier.shouldListenToLightSensor()).thenReturn(true); - mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_ON); + mClamperController.clamp(mDisplayBrightnessState, mMockRequest, initialBrightness, + initialSlowChange, STATE_ON); verify(mMockLightSensorController).restart(); } @@ -214,7 +218,8 @@ public class BrightnessClamperControllerTest { float initialBrightness = 0.2f; boolean initialSlowChange = true; clearInvocations(mMockLightSensorController); - mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_OFF); + mClamperController.clamp(mDisplayBrightnessState, mMockRequest, initialBrightness, + initialSlowChange, STATE_OFF); verify(mMockLightSensorController).stop(); } @@ -232,8 +237,8 @@ public class BrightnessClamperControllerTest { mTestInjector.mCapturedChangeListener.onChanged(); mTestHandler.flush(); - DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness, - initialSlowChange, STATE_ON); + DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState, + mMockRequest, initialBrightness, initialSlowChange, STATE_ON); assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE); assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE); @@ -256,8 +261,8 @@ public class BrightnessClamperControllerTest { mTestInjector.mCapturedChangeListener.onChanged(); mTestHandler.flush(); - DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness, - initialSlowChange, STATE_ON); + DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState, + mMockRequest, initialBrightness, initialSlowChange, STATE_ON); assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE); assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE); @@ -280,8 +285,8 @@ public class BrightnessClamperControllerTest { mTestInjector.mCapturedChangeListener.onChanged(); mTestHandler.flush(); - DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness, - initialSlowChange, STATE_ON); + DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState, + mMockRequest, initialBrightness, initialSlowChange, STATE_ON); assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE); assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE); @@ -304,11 +309,11 @@ public class BrightnessClamperControllerTest { mTestInjector.mCapturedChangeListener.onChanged(); mTestHandler.flush(); // first call of clamp method - mClamperController.clamp(mMockRequest, initialBrightness, + mClamperController.clamp(mDisplayBrightnessState, mMockRequest, initialBrightness, initialSlowChange, STATE_ON); // immediately second call of clamp method - DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness, - initialSlowChange, STATE_ON); + DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState, + mMockRequest, initialBrightness, initialSlowChange, STATE_ON); assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE); assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE); @@ -319,6 +324,22 @@ public class BrightnessClamperControllerTest { } @Test + public void testClamp_activeClamperApplied_confirmBrightnessOverrideStateReturned() { + float initialBrightness = 0.8f; + boolean initialSlowChange = false; + mTestInjector.mCapturedChangeListener.onChanged(); + mTestHandler.flush(); + + mDisplayBrightnessState = DisplayBrightnessState.builder().setBrightnessReason( + BrightnessReason.REASON_OVERRIDE).build(); + + DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState, + mMockRequest, initialBrightness, initialSlowChange, STATE_ON); + + assertEquals(BrightnessReason.REASON_OVERRIDE, state.getBrightnessReason().getReason()); + } + + @Test public void testAmbientLuxChanges() { mTestInjector.mCapturedLightSensorListener.onAmbientLuxChange(50); diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt index 3c77ec925078..3aef6aa2ee3f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt @@ -97,11 +97,14 @@ class BrightnessObserverTest { private fun setUpLowBrightnessZone() { whenever(mockInjector.getBrightnessInfo(Display.DEFAULT_DISPLAY)).thenReturn( - BrightnessInfo(/* brightness = */ 0.05f, /* adjustedBrightness = */ 0.05f, - /* brightnessMinimum = */ 0.0f, /* brightnessMaximum = */ 1.0f, - BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - /* highBrightnessTransitionPoint = */ 1.0f, - BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)) + BrightnessInfo(/* brightness = */ 0.05f, /* adjustedBrightness = */ 0.05f, + /* brightnessMinimum = */ 0.0f, /* brightnessMaximum = */ 1.0f, + BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, + /* highBrightnessTransitionPoint = */ 1.0f, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, + false /* isBrightnessOverrideByWindow */ + ) + ) whenever(mockDeviceConfig.highDisplayBrightnessThresholds).thenReturn(floatArrayOf()) whenever(mockDeviceConfig.highAmbientBrightnessThresholds).thenReturn(floatArrayOf()) whenever(mockDeviceConfig.lowDisplayBrightnessThresholds).thenReturn(floatArrayOf(0.1f)) diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index 58f0ab4411bc..4e0bab8bf4bd 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -1225,8 +1225,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener displayListener = displayListenerCaptor.getValue(); setBrightness(10, 10, displayListener); @@ -1256,8 +1256,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener displayListener = displayListenerCaptor.getValue(); setBrightness(10, 10, displayListener); @@ -1291,8 +1291,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener displayListener = displayListenerCaptor.getValue(); setBrightness(10, 10, displayListener); @@ -1325,8 +1325,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener displayListener = displayListenerCaptor.getValue(); ArgumentCaptor<SensorEventListener> sensorListenerCaptor = @@ -1404,8 +1404,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener displayListener = displayListenerCaptor.getValue(); ArgumentCaptor<SensorEventListener> sensorListenerCaptor = @@ -1464,8 +1464,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener displayListener = displayListenerCaptor.getValue(); ArgumentCaptor<SensorEventListener> listenerCaptor = @@ -1630,8 +1630,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener displayListener = displayListenerCaptor.getValue(); // Get the sensor listener so that we can give it new light sensor events @@ -1730,8 +1730,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener displayListener = displayListenerCaptor.getValue(); // Get the sensor listener so that we can give it new light sensor events @@ -2877,8 +2877,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener listener = captor.getValue(); // Specify Limitation @@ -3000,8 +3000,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener listener = captor.getValue(); final int initialRefreshRate = 60; @@ -3075,8 +3075,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener listener = captor.getValue(); // Specify Limitation for different display @@ -3115,8 +3115,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener listener = captor.getValue(); // Specify Limitation @@ -3200,8 +3200,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener listener = captor.getValue(); // Specify Sunlight limitations @@ -3239,8 +3239,8 @@ public class DisplayModeDirectorTest { ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class); verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class), - eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED)); + eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED), + eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS)); DisplayListener listener = captor.getValue(); // Specify Limitation for different display @@ -3786,8 +3786,9 @@ public class DisplayModeDirectorTest { when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(floatBri, floatAdjBri, 0.0f, 1.0f, - BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT, - BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); + BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, + false /* isBrightnessOverrideByWindow */)); listener.onDisplayChanged(DISPLAY_ID); } @@ -3897,7 +3898,12 @@ public class DisplayModeDirectorTest { public void registerDisplayListener(DisplayListener listener, Handler handler) {} @Override - public void registerDisplayListener(DisplayListener listener, Handler handler, long flag) {} + public void registerDisplayListener(DisplayListener listener, Handler handler, + long flags) {} + + @Override + public void registerDisplayListener(DisplayListener listener, Handler handler, long flag, + long privateFlag) {} @Override public Display getDisplay(int displayId) { diff --git a/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java new file mode 100644 index 000000000000..5e2f80bf8311 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java @@ -0,0 +1,338 @@ +/* + * 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; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.content.Context; +import android.hardware.health.HealthInfo; +import android.os.HandlerThread; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.internal.R; +import com.android.internal.app.IBatteryStats; +import com.android.modules.utils.testing.ExtendedMockitoRule; +import com.android.server.am.BatteryStatsService; +import com.android.server.flags.Flags; +import com.android.server.lights.LightsManager; +import com.android.server.lights.LogicalLight; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public class BatteryServiceTest { + + private static final int CURRENT_BATTERY_VOLTAGE = 3000; + private static final int VOLTAGE_LESS_THEN_ONE_PERCENT = 3029; + private static final int VOLTAGE_MORE_THEN_ONE_PERCENT = 3030; + private static final int CURRENT_BATTERY_TEMP = 300; + private static final int TEMP_LESS_THEN_ONE_DEGREE_CELSIUS = 305; + private static final int TEMP_MORE_THEN_ONE_DEGREE_CELSIUS = 310; + private static final int CURRENT_BATTERY_HEALTH = 2; + private static final int UPDATED_BATTERY_HEALTH = 3; + private static final int CURRENT_CHARGE_COUNTER = 4680000; + private static final int UPDATED_CHARGE_COUNTER = 4218000; + private static final int HANDLER_IDLE_TIME_MS = 5000; + @Rule + public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this) + .mockStatic(SystemProperties.class) + .mockStatic(ActivityManager.class) + .mockStatic(BatteryStatsService.class) + .build(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock + private Context mContextMock; + @Mock + private LightsManager mLightsManagerMock; + @Mock + private ActivityManagerInternal mActivityManagerInternalMock; + @Mock + private IBatteryStats mIBatteryStatsMock; + + private BatteryService mBatteryService; + private String mSystemUiPackage; + + /** + * Creates a mock and registers it to {@link LocalServices}. + */ + private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, mock); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mSystemUiPackage = InstrumentationRegistry.getInstrumentation().getTargetContext() + .getResources().getString(R.string.config_systemUi); + + when(mLightsManagerMock.getLight(anyInt())).thenReturn(mock(LogicalLight.class)); + when(mActivityManagerInternalMock.isSystemReady()).thenReturn(true); + when(mContextMock.getResources()).thenReturn( + InstrumentationRegistry.getInstrumentation().getTargetContext().getResources()); + ExtendedMockito.when(BatteryStatsService.getService()).thenReturn(mIBatteryStatsMock); + + doNothing().when(mIBatteryStatsMock).setBatteryState(anyInt(), anyInt(), anyInt(), anyInt(), + anyInt(), anyInt(), anyInt(), anyInt(), anyLong()); + doNothing().when(() -> SystemProperties.set(anyString(), anyString())); + doNothing().when(() -> ActivityManager.broadcastStickyIntent(any(), + eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE), + eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL))); + + addLocalServiceMock(LightsManager.class, mLightsManagerMock); + addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock); + + createBatteryService(); + } + + @Test + public void createBatteryService_withNullLooper_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new BatteryService(mContextMock)); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void onlyVoltageUpdated_lessThenOnePercent_broadcastNotSent() { + mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP, + CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(0); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void onlyVoltageUpdated_beforeTwentySeconds_broadcastNotSent() { + mBatteryService.update( + createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP, + CURRENT_CHARGE_COUNTER, + CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(0); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void onlyVoltageUpdated_broadcastSent() { + mBatteryService.mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime() - 20000; + mBatteryService.update(createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP, + CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(1); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void onlyTempUpdated_lessThenOneDegreeCelsius_broadcastNotSent() { + mBatteryService.update( + createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS, + CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(0); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void tempUpdated_broadcastSent() { + long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime; + mBatteryService.update( + createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, TEMP_MORE_THEN_ONE_DEGREE_CELSIUS, + UPDATED_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime); + verifyNumberOfTimesBroadcastSent(1); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void batteryHealthUpdated_voltageAndTempConst_broadcastSent() { + mBatteryService.update( + createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP, + CURRENT_CHARGE_COUNTER, + UPDATED_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(1); + + // updating counter just after the health update does not triggers broadcast. + mBatteryService.update( + createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP, + UPDATED_CHARGE_COUNTER, + UPDATED_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(1); + } + + @Test + @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void voltageUpdated_lessThanOnePercent_flagDisabled_broadcastSent() { + mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP, + CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(1); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void onlyChargeCounterUpdated_broadcastNotSent() { + mBatteryService.update( + createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP, + UPDATED_CHARGE_COUNTER, + CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(0); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void chargeCounterUpdated_tempUpdatedLessThanOneDegree_broadcastNotSent() { + mBatteryService.update( + createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS, + UPDATED_CHARGE_COUNTER, + CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(0); + } + + @Test + @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void onlyChargeCounterUpdated_broadcastSent() { + mBatteryService.update( + createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP, + UPDATED_CHARGE_COUNTER, + CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(1); + } + + private HealthInfo createHealthInfo( + int batteryVoltage, + int batteryTemperature, + int batteryChargeCounter, + int batteryHealth) { + HealthInfo h = new HealthInfo(); + h.batteryVoltageMillivolts = batteryVoltage; + h.batteryTemperatureTenthsCelsius = batteryTemperature; + h.batteryChargeCounterUah = batteryChargeCounter; + h.batteryStatus = 5; + h.batteryHealth = batteryHealth; + h.batteryPresent = true; + h.batteryLevel = 100; + h.maxChargingCurrentMicroamps = 298125; + h.batteryCurrentAverageMicroamps = -2812; + h.batteryCurrentMicroamps = 298125; + h.maxChargingVoltageMicrovolts = 3000; + h.batteryCycleCount = 50; + h.chargingState = 4; + h.batteryCapacityLevel = 100; + return h; + } + + // Creates a new battery service objects and sets the initial values. + private void createBatteryService() throws InterruptedException { + final HandlerThread handlerThread = new HandlerThread("BatteryServiceTest"); + handlerThread.start(); + + mBatteryService = new BatteryService(mContextMock, handlerThread.getLooper()); + + // trigger the update to set the initial values. + mBatteryService.update( + createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP, + CURRENT_CHARGE_COUNTER, + CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + } + + private void waitForHandlerToExecute() { + final CountDownLatch latch = new CountDownLatch(1); + mBatteryService.getHandlerForTest().post(latch::countDown); + boolean isExecutionComplete = false; + + try { + isExecutionComplete = latch.await(HANDLER_IDLE_TIME_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + fail("Handler interrupted before executing the message " + e); + } + + assertTrue("Timed out while waiting for Handler to execute.", isExecutionComplete); + } + + private void verifyNumberOfTimesBroadcastSent(int numberOfTimes) { + // Increase the numberOfTimes by 1 as one broadcast was sent initially during the test + // setUp. + verify(() -> ActivityManager.broadcastStickyIntent(any(), + eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE), + eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL)), + times(++numberOfTimes)); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java index 2a825f35bf62..dcbc23410fdb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -1308,6 +1308,8 @@ public class ActivityManagerServiceTest { Intent intent = new Intent(); Intent extraIntent = new Intent("EXTRA_INTENT_ACTION"); intent.putExtra("EXTRA_INTENT0", extraIntent); + Intent nestedIntent = new Intent("NESTED_INTENT_ACTION"); + extraIntent.putExtra("NESTED_INTENT", nestedIntent); intent.collectExtraIntentKeys(); mAms.addCreatorToken(intent, TEST_PACKAGE); @@ -1317,6 +1319,11 @@ public class ActivityManagerServiceTest { assertThat(token).isNotNull(); assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid()); assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE); + + token = (ActivityManagerService.IntentCreatorToken) nestedIntent.getCreatorToken(); + assertThat(token).isNotNull(); + assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid()); + assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE); } @Test @@ -1349,6 +1356,8 @@ public class ActivityManagerServiceTest { Intent intent = new Intent(); Intent extraIntent = new Intent("EXTRA_INTENT_ACTION"); intent.putExtra("EXTRA_INTENT", extraIntent); + Intent nestedIntent = new Intent("NESTED_INTENT_ACTION"); + extraIntent.putExtra("NESTED_INTENT", nestedIntent); intent.collectExtraIntentKeys(); @@ -1374,9 +1383,12 @@ public class ActivityManagerServiceTest { extraIntent = intent.getParcelableExtra("EXTRA_INTENT", Intent.class); extraIntent2 = intent.getParcelableExtra("EXTRA_INTENT2", Intent.class); extraIntent3 = intent.getParcelableExtra("EXTRA_INTENT3", Intent.class); + nestedIntent = extraIntent.getParcelableExtra("NESTED_INTENT", Intent.class); assertThat(extraIntent.getExtendedFlags() & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0); + assertThat(nestedIntent.getExtendedFlags() + & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0); // sneaked in intent should have EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN set. assertThat(extraIntent2.getExtendedFlags() & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isNotEqualTo(0); diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS b/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS deleted file mode 100644 index 0218a7835586..000000000000 --- a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java index 685e8d6a3bc5..e611867493eb 100644 --- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java @@ -65,7 +65,7 @@ public class ContextHubServiceTest { new Pair<>(Arrays.asList(mMockContextHubInfo), Arrays.asList("")); when(mMockContextHubInfo.getId()).thenReturn(CONTEXT_HUB_ID); when(mMockContextHubInfo.toString()).thenReturn(CONTEXT_HUB_STRING); - when(mMockContextHubWrapper.getHubs()).thenReturn(hubInfo); + when(mMockContextHubWrapper.getContextHubs()).thenReturn(hubInfo); when(mMockContextHubWrapper.supportsLocationSettingNotifications()).thenReturn(true); when(mMockContextHubWrapper.supportsWifiSettingNotifications()).thenReturn(true); diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index dddab657be14..5a7027edc20d 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -2158,13 +2158,11 @@ public class NetworkPolicyManagerServiceTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testBackgroundChainEnabled() throws Exception { verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true); } @Test - @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) @RequiresFlagsDisabled(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN) public void testBackgroundChainOnProcStateChangeSameDelay() throws Exception { // initialization calls setFirewallChainEnabled, so we want to reset the invocations. @@ -2194,10 +2192,7 @@ public class NetworkPolicyManagerServiceTest { } @Test - @RequiresFlagsEnabled({ - Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE, - Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN - }) + @RequiresFlagsEnabled(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN) public void testBackgroundChainOnProcStateChangeDifferentDelays() throws Exception { // The app will be blocked when there is no prior proc-state. assertTrue(mService.isUidNetworkingBlocked(UID_A, false)); @@ -2247,7 +2242,6 @@ public class NetworkPolicyManagerServiceTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testBackgroundChainOnAllowlistChange() throws Exception { // initialization calls setFirewallChainEnabled, so we want to reset the invocations. clearInvocations(mNetworkManager); @@ -2285,7 +2279,6 @@ public class NetworkPolicyManagerServiceTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testBackgroundChainOnTempAllowlistChange() throws Exception { // initialization calls setFirewallChainEnabled, so we want to reset the invocations. clearInvocations(mNetworkManager); @@ -2387,7 +2380,6 @@ public class NetworkPolicyManagerServiceTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testUidObserverFiltersProcStateChanges() throws Exception { int testProcStateSeq = 0; try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { @@ -2450,7 +2442,6 @@ public class NetworkPolicyManagerServiceTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testUidObserverFiltersStaleChanges() throws Exception { final int testProcStateSeq = 51; try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { @@ -2470,7 +2461,6 @@ public class NetworkPolicyManagerServiceTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testUidObserverFiltersCapabilityChanges() throws Exception { int testProcStateSeq = 0; try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { @@ -2559,7 +2549,6 @@ public class NetworkPolicyManagerServiceTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testObsoleteHandleUidChanged() throws Exception { callAndWaitOnUidGone(UID_A); assertTrue(mService.isUidNetworkingBlocked(UID_A, false)); diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java index d1806881ee37..154494a13072 100644 --- a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.android.server.adaptiveauth; +package com.android.server.security.adaptiveauthentication; import static android.adaptiveauth.Flags.FLAG_ENABLE_ADAPTIVE_AUTH; import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS; import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; -import static com.android.server.adaptiveauth.AdaptiveAuthService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; +import static com.android.server.security.adaptiveauthentication.AdaptiveAuthenticationService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; import static org.junit.Assert.assertEquals; import static org.junit.Assume.assumeTrue; @@ -66,12 +66,12 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** - * atest FrameworksServicesTests:AdaptiveAuthServiceTest + * atest FrameworksServicesTests:AdaptiveAuthenticationServiceTest */ @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) -public class AdaptiveAuthServiceTest { +public class AdaptiveAuthenticationServiceTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -81,7 +81,7 @@ public class AdaptiveAuthServiceTest { private static final int REASON_UNKNOWN = 0; // BiometricRequestConstants.RequestReason private Context mContext; - private AdaptiveAuthService mAdaptiveAuthService; + private AdaptiveAuthenticationService mAdaptiveAuthenticationService; @Mock LockPatternUtils mLockPatternUtils; @@ -124,8 +124,9 @@ public class AdaptiveAuthServiceTest { LocalServices.removeServiceForTest(UserManagerInternal.class); LocalServices.addService(UserManagerInternal.class, mUserManager); - mAdaptiveAuthService = new AdaptiveAuthService(mContext, mLockPatternUtils); - mAdaptiveAuthService.init(); + mAdaptiveAuthenticationService = new AdaptiveAuthenticationService( + mContext, mLockPatternUtils); + mAdaptiveAuthenticationService.init(); verify(mLockSettings).registerLockSettingsStateListener( mLockSettingsStateListenerCaptor.capture()); @@ -317,13 +318,13 @@ public class AdaptiveAuthServiceTest { private void verifyNotLockDevice(int expectedCntFailedAttempts, int userId) { assertEquals(expectedCntFailedAttempts, - mAdaptiveAuthService.mFailedAttemptsForUser.get(userId)); + mAdaptiveAuthenticationService.mFailedAttemptsForUser.get(userId)); verify(mWindowManager, never()).lockNow(); } private void verifyLockDevice(int userId) { assertEquals(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS, - mAdaptiveAuthService.mFailedAttemptsForUser.get(userId)); + mAdaptiveAuthenticationService.mFailedAttemptsForUser.get(userId)); verify(mLockPatternUtils).requireStrongAuth( eq(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST), eq(userId)); // If userId is MANAGED_PROFILE_USER_ID, the StrongAuthFlag of its parent (PRIMARY_USER_ID) diff --git a/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS new file mode 100644 index 000000000000..bc8efa92c16f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
\ No newline at end of file diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java index 0b89c11a11f4..38ff3a22022d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java @@ -2291,7 +2291,9 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_CLASSIFICATION, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) public void testMoveAggregateGroups_updateChannel_multipleChannels_regroupOnClassifEnabled() { final String pkg = "package"; final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg, @@ -2366,7 +2368,9 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_CLASSIFICATION, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) public void testMoveSections_notificationBundled() { final List<NotificationRecord> notificationList = new ArrayList<>(); final String pkg = "package"; @@ -2436,7 +2440,9 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_CLASSIFICATION, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) public void testCacheAndCancelAppSummary_notificationBundled() { // check that the original app summary is canceled & cached on classification regrouping final List<NotificationRecord> notificationList = new ArrayList<>(); @@ -2495,6 +2501,7 @@ public class GroupHelperTest extends UiServiceTestCase { @Test @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS}) public void testSingletonGroupsRegrouped_notificationBundledBeforeDelayTimeout() { @@ -2569,6 +2576,7 @@ public class GroupHelperTest extends UiServiceTestCase { @Test @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS}) public void testSingletonGroupsRegrouped_notificationBundledAfterDelayTimeout() { diff --git a/services/tests/vibrator/AndroidManifest.xml b/services/tests/vibrator/AndroidManifest.xml index c0f514fb9673..850884f84b01 100644 --- a/services/tests/vibrator/AndroidManifest.xml +++ b/services/tests/vibrator/AndroidManifest.xml @@ -32,6 +32,9 @@ <uses-permission android:name="android.permission.VIBRATE_ALWAYS_ON" /> <!-- Required to play system-only haptic feedback constants --> <uses-permission android:name="android.permission.VIBRATE_SYSTEM_CONSTANTS" /> + <!-- Required to play vendor effects and start vendor sessions --> + <uses-permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS" /> + <uses-permission android:name="android.permission.START_VIBRATION_SESSIONS" /> <application android:debuggable="true"> <uses-library android:name="android.test.mock" android:required="true" /> diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index dfdd0cde6aba..88ba9e3af6df 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -35,6 +35,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -83,6 +84,8 @@ import android.os.Vibrator; import android.os.VibratorInfo; import android.os.test.FakeVibrator; import android.os.test.TestLooper; +import android.os.vibrator.IVibrationSession; +import android.os.vibrator.IVibrationSessionCallback; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.StepSegment; @@ -195,6 +198,7 @@ public class VibratorManagerServiceTest { new SparseArray<>(); private final List<HalVibration> mPendingVibrations = new ArrayList<>(); + private final List<VendorVibrationSession> mPendingSessions = new ArrayList<>(); private VibratorManagerService mService; private Context mContextSpy; @@ -264,6 +268,11 @@ public class VibratorManagerServiceTest { grantPermission(android.Manifest.permission.VIBRATE); // Cancel any pending vibration from tests, including external vibrations. cancelVibrate(mService); + // End pending sessions. + for (VendorVibrationSession session : mPendingSessions) { + session.cancelSession(); + } + mTestLooper.dispatchAll(); // Wait until pending vibrations end asynchronously. for (HalVibration vibration : mPendingVibrations) { vibration.waitForEnd(); @@ -1229,6 +1238,36 @@ public class VibratorManagerServiceTest { .anyMatch(PrebakedSegment.class::isInstance)); } + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + @Test + public void vibrate_withOngoingHigherImportanceSession_ignoresEffect() throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1); + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); + fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); + VibratorManagerService service = createSystemReadyService(); + IVibrationSessionCallback callback = + mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS); + + VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1); + mTestLooper.dispatchAll(); + assertThat(session.getStatus()).isEqualTo(Status.RUNNING); + verify(callback).onStarted(any(IVibrationSession.class)); + + HalVibration vibration = vibrateAndWaitUntilFinished(service, + VibrationEffect.get(VibrationEffect.EFFECT_CLICK), + HAPTIC_FEEDBACK_ATTRS); + mTestLooper.dispatchAll(); + + assertThat(session.getStatus()).isEqualTo(Status.RUNNING); + assertThat(vibration.getStatus()).isEqualTo(Status.IGNORED_FOR_HIGHER_IMPORTANCE); + verify(callback, never()).onFinishing(); + verify(callback, never()).onFinished(anyInt()); + // The second vibration shouldn't have played any prebaked segment. + assertFalse(fakeVibrator.getAllEffectSegments().stream() + .anyMatch(PrebakedSegment.class::isInstance)); + } + @Test public void vibrate_withOngoingLowerImportanceVibration_cancelsOngoingEffect() throws Exception { @@ -1289,6 +1328,36 @@ public class VibratorManagerServiceTest { .filter(PrebakedSegment.class::isInstance).count()); } + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + @Test + public void vibrate_withOngoingLowerImportanceSession_cancelsOngoingSession() throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1); + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); + fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); + VibratorManagerService service = createSystemReadyService(); + IVibrationSessionCallback callback = + mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS); + + VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1); + mTestLooper.dispatchAll(); + assertThat(session.getStatus()).isEqualTo(Status.RUNNING); + verify(callback).onStarted(any(IVibrationSession.class)); + + HalVibration vibration = vibrateAndWaitUntilFinished(service, + VibrationEffect.get(VibrationEffect.EFFECT_CLICK), + HAPTIC_FEEDBACK_ATTRS); + mTestLooper.dispatchAll(); + + assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED); + assertThat(vibration.getStatus()).isEqualTo(Status.FINISHED); + verify(callback).onFinishing(); + verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED)); + // One segment played is the prebaked CLICK from the new vibration. + assertEquals(1, mVibratorProviders.get(1).getAllEffectSegments().stream() + .filter(PrebakedSegment.class::isInstance).count()); + } + @Test public void vibrate_withOngoingSameImportancePipelinedVibration_continuesOngoingEffect() throws Exception { @@ -1416,16 +1485,16 @@ public class VibratorManagerServiceTest { // The native callback will be dispatched manually in this test. mTestLooper.stopAutoDispatchAndIgnoreExceptions(); - ArgumentCaptor<VibratorManagerService.OnSyncedVibrationCompleteListener> listenerCaptor = + ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor = ArgumentCaptor.forClass( - VibratorManagerService.OnSyncedVibrationCompleteListener.class); + VibratorManagerService.VibratorManagerNativeCallbacks.class); verify(mNativeWrapperMock).init(listenerCaptor.capture()); CountDownLatch triggerCountDown = new CountDownLatch(1); // Mock trigger callback on registered listener right after the synced vibration starts. when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true); when(mNativeWrapperMock.triggerSynced(anyLong())).then(answer -> { - listenerCaptor.getValue().onComplete(answer.getArgument(0)); + listenerCaptor.getValue().onSyncedVibrationComplete(answer.getArgument(0)); triggerCountDown.countDown(); return true; }); @@ -2318,6 +2387,34 @@ public class VibratorManagerServiceTest { assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates()); } + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + @Test + public void onExternalVibration_withOngoingHigherImportanceSession_ignoreNewVibration() + throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1); + mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); + VibratorManagerService service = createSystemReadyService(); + IVibrationSessionCallback callback = + mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS); + + VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1); + mTestLooper.dispatchAll(); + verify(callback).onStarted(any(IVibrationSession.class)); + + ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, + AUDIO_ALARM_ATTRS, mock(IExternalVibrationController.class)); + ExternalVibrationScale scale = + mExternalVibratorService.onExternalVibrationStart(externalVibration); + // External vibration is ignored. + assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel); + + // Session still running. + assertThat(session.getStatus()).isEqualTo(Status.RUNNING); + verify(callback, never()).onFinishing(); + verify(callback, never()).onFinished(anyInt()); + } + @Test public void onExternalVibration_withNewSameImportanceButRepeating_cancelsOngoingVibration() throws Exception { @@ -2373,6 +2470,36 @@ public class VibratorManagerServiceTest { assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates()); } + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + @Test + public void onExternalVibration_withOngoingLowerImportanceSession_cancelsOngoingSession() + throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1); + mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); + VibratorManagerService service = createSystemReadyService(); + IVibrationSessionCallback callback = + mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS); + + VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1); + mTestLooper.dispatchAll(); + verify(callback).onStarted(any(IVibrationSession.class)); + + ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, + AUDIO_ALARM_ATTRS, mock(IExternalVibrationController.class)); + ExternalVibrationScale scale = + mExternalVibratorService.onExternalVibrationStart(externalVibration); + assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel); + mTestLooper.dispatchAll(); + + // Session is cancelled. + assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED); + verify(callback).onFinishing(); + verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED)); + assertEquals(Arrays.asList(false, true), + mVibratorProviders.get(1).getExternalControlStates()); + } + @Test public void onExternalVibration_withRingtone_usesRingerModeSettings() { mockVibrators(1); @@ -2638,6 +2765,376 @@ public class VibratorManagerServiceTest { } @Test + @DisableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_withoutFeatureFlag_throwsException() throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + int vibratorId = 1; + mockVibrators(vibratorId); + VibratorManagerService service = createSystemReadyService(); + + IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class); + assertThrows("Expected starting session without feature flag to fail!", + UnsupportedOperationException.class, + () -> startSession(service, RINGTONE_ATTRS, callback, vibratorId)); + mTestLooper.dispatchAll(); + + verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class)); + verify(callback, never()).onStarted(any(IVibrationSession.class)); + verify(callback, never()).onFinishing(); + verify(callback, never()).onFinished(anyInt()); + } + + @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_withoutCapability_doesNotStart() throws Exception { + int vibratorId = 1; + mockVibrators(vibratorId); + VibratorManagerService service = createSystemReadyService(); + + IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class); + VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, + callback, vibratorId); + mTestLooper.dispatchAll(); + + assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED); + verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class)); + verify(callback, never()).onFinishing(); + verify(callback) + .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED)); + } + + @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_withoutCallback_doesNotStart() { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + int vibratorId = 1; + mockVibrators(vibratorId); + VibratorManagerService service = createSystemReadyService(); + + VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, + /* callback= */ null, vibratorId); + mTestLooper.dispatchAll(); + + assertThat(session).isNull(); + verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class)); + } + + @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_withoutVibratorIds_doesNotStart() throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1); + VibratorManagerService service = createSystemReadyService(); + + int[] nullIds = null; + IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class); + VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, nullIds); + assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED); + + int[] emptyIds = {}; + session = startSession(service, RINGTONE_ATTRS, callback, emptyIds); + assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED); + + mTestLooper.dispatchAll(); + + verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class)); + verify(callback, never()).onFinishing(); + verify(callback, times(2)) + .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED)); + } + + @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_badVibratorId_failsToStart() throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1, 2); + when(mNativeWrapperMock.startSession(anyLong(), any(int[].class))).thenReturn(false); + doReturn(false).when(mNativeWrapperMock).startSession(anyLong(), eq(new int[] {1, 3})); + doReturn(true).when(mNativeWrapperMock).startSession(anyLong(), eq(new int[] {1, 2})); + VibratorManagerService service = createSystemReadyService(); + + IBinder token = mock(IBinder.class); + IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class); + doReturn(token).when(callback).asBinder(); + + VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 3); + mTestLooper.dispatchAll(); + + assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED); + verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 3})); + verify(callback, never()).onStarted(any(IVibrationSession.class)); + verify(callback, never()).onFinishing(); + verify(callback) + .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED)); + } + + @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_thenFinish_returnsSuccessAfterCallback() throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1, 2); + VibratorManagerService service = createSystemReadyService(); + int sessionFinishDelayMs = 200; + IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs); + + VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); + mTestLooper.dispatchAll(); + + verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); + ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); + verify(callback).onStarted(captor.capture()); + + captor.getValue().finishSession(); + + // Session not ended until HAL callback. + assertThat(session.getStatus()).isEqualTo(Status.RUNNING); + + // Dispatch HAL callbacks. + mTestLooper.moveTimeForward(sessionFinishDelayMs); + mTestLooper.dispatchAll(); + + assertThat(session.getStatus()).isEqualTo(Status.FINISHED); + verify(callback).onFinishing(); + verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS)); + } + + @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_thenSendCancelSignal_cancelsSession() throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1, 2); + VibratorManagerService service = createSystemReadyService(); + int sessionFinishDelayMs = 200; + IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs); + + VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); + mTestLooper.dispatchAll(); + + verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); + ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); + verify(callback).onStarted(captor.capture()); + + session.getCancellationSignal().cancel(); + mTestLooper.dispatchAll(); + + assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER); + verify(callback).onFinishing(); + verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED)); + } + + @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_thenCancel_returnsCancelStatus() throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1, 2); + VibratorManagerService service = createSystemReadyService(); + // Delay not applied when session is aborted. + IVibrationSessionCallback callback = + mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS); + + VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); + mTestLooper.dispatchAll(); + + verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); + ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); + verify(callback).onStarted(captor.capture()); + + captor.getValue().cancelSession(); + mTestLooper.dispatchAll(); + + assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER); + verify(callback).onFinishing(); + verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED)); + } + + @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_finishThenCancel_returnsRightAwayWithFinishedStatus() + throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1, 2); + VibratorManagerService service = createSystemReadyService(); + // Delay not applied when session is aborted. + IVibrationSessionCallback callback = + mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS); + + VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); + mTestLooper.dispatchAll(); + + verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); + ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); + verify(callback).onStarted(captor.capture()); + + captor.getValue().finishSession(); + mTestLooper.dispatchAll(); + assertThat(session.getStatus()).isEqualTo(Status.RUNNING); + + captor.getValue().cancelSession(); + mTestLooper.dispatchAll(); + + assertThat(session.getStatus()).isEqualTo(Status.FINISHED); + verify(callback).onFinishing(); + verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS)); + } + + @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_thenHalCancels_returnsCancelStatus() + throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1, 2); + VibratorManagerService service = createSystemReadyService(); + ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor = + ArgumentCaptor.forClass( + VibratorManagerService.VibratorManagerNativeCallbacks.class); + verify(mNativeWrapperMock).init(listenerCaptor.capture()); + doReturn(true).when(mNativeWrapperMock).startSession(anyLong(), any(int[].class)); + + IBinder token = mock(IBinder.class); + IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class); + doReturn(token).when(callback).asBinder(); + VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); + mTestLooper.dispatchAll(); + + verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); + verify(callback).onStarted(any(IVibrationSession.class)); + + // Mock HAL ending session unexpectedly. + listenerCaptor.getValue().onVibrationSessionComplete(session.getSessionId()); + mTestLooper.dispatchAll(); + + assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_UNKNOWN_REASON); + verify(callback).onFinishing(); + verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED)); + } + + @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_withPowerMode_usesPowerModeState() throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1); + VibratorManagerService service = createSystemReadyService(); + IVibrationSessionCallback callback = + mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS); + + mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); + VendorVibrationSession session1 = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1); + VendorVibrationSession session2 = startSession(service, RINGTONE_ATTRS, callback, 1); + mTestLooper.dispatchAll(); + + ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); + verify(callback).onStarted(captor.capture()); + captor.getValue().cancelSession(); + mTestLooper.dispatchAll(); + + mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); + VendorVibrationSession session3 = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1); + mTestLooper.dispatchAll(); + + verify(mNativeWrapperMock, never()) + .startSession(eq(session1.getSessionId()), any(int[].class)); + verify(mNativeWrapperMock).startSession(eq(session2.getSessionId()), eq(new int[] {1})); + verify(mNativeWrapperMock).startSession(eq(session3.getSessionId()), eq(new int[] {1})); + } + + @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_withOngoingHigherImportanceVibration_ignoresSession() + throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1); + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); + fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); + VibratorManagerService service = createSystemReadyService(); + IVibrationSessionCallback callback = + mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS); + + VibrationEffect effect = VibrationEffect.createWaveform( + new long[]{10, 10_000}, new int[]{128, 255}, -1); + vibrate(service, effect, ALARM_ATTRS); + + // VibrationThread will start this vibration async. + // Wait until second step started to ensure the noteVibratorOn was triggered. + assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2, + service, TEST_TIMEOUT_MILLIS)); + + VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1); + mTestLooper.dispatchAll(); + + verify(mNativeWrapperMock, never()) + .startSession(eq(session.getSessionId()), any(int[].class)); + assertThat(session.getStatus()).isEqualTo(Status.IGNORED_FOR_HIGHER_IMPORTANCE); + verify(callback, never()).onFinishing(); + verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_IGNORED)); + } + + @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_withOngoingLowerImportanceVibration_cancelsOngoing() + throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1); + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); + fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); + fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK); + VibratorManagerService service = createSystemReadyService(); + IVibrationSessionCallback callback = + mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS); + + VibrationEffect effect = VibrationEffect.createWaveform( + new long[]{10, 10_000}, new int[]{128, 255}, -1); + HalVibration vibration = vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS); + + // VibrationThread will start this vibration async. + // Wait until second step started to ensure the noteVibratorOn was triggered. + assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2, service, + TEST_TIMEOUT_MILLIS)); + + VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1); + vibration.waitForEnd(); + assertTrue(waitUntil(s -> session.isStarted(), service, TEST_TIMEOUT_MILLIS)); + mTestLooper.dispatchAll(); + + assertThat(vibration.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED); + assertThat(session.getStatus()).isEqualTo(Status.RUNNING); + verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] { 1 })); + verify(callback).onStarted(any(IVibrationSession.class)); + } + + @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void startVibrationSession_withOngoingLowerImportanceExternalVibration_cancelsOngoing() + throws Exception { + mockCapabilities(IVibratorManager.CAP_START_SESSIONS); + mockVibrators(1); + mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); + mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK); + setRingerMode(AudioManager.RINGER_MODE_NORMAL); + VibratorManagerService service = createSystemReadyService(); + IVibrationSessionCallback callback = + mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS); + + IBinder firstToken = mock(IBinder.class); + IExternalVibrationController controller = mock(IExternalVibrationController.class); + ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, + AUDIO_ALARM_ATTRS, + controller, firstToken); + ExternalVibrationScale scale = + mExternalVibratorService.onExternalVibrationStart(externalVibration); + + VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1); + mTestLooper.dispatchAll(); + + assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel); + // The external vibration should have been cancelled + verify(controller).mute(); + assertEquals(Arrays.asList(false, true, false), + mVibratorProviders.get(1).getExternalControlStates()); + verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] { 1 })); + verify(callback).onStarted(any(IVibrationSession.class)); + } + + @Test public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); @@ -3050,6 +3547,30 @@ public class VibratorManagerServiceTest { when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds); } + private IVibrationSessionCallback mockSessionCallbacks(long delayToEndSessionMillis) { + Handler handler = new Handler(mTestLooper.getLooper()); + ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor = + ArgumentCaptor.forClass( + VibratorManagerService.VibratorManagerNativeCallbacks.class); + verify(mNativeWrapperMock).init(listenerCaptor.capture()); + doReturn(true).when(mNativeWrapperMock).startSession(anyLong(), any(int[].class)); + doAnswer(args -> { + handler.postDelayed( + () -> listenerCaptor.getValue().onVibrationSessionComplete(args.getArgument(0)), + delayToEndSessionMillis); + return null; + }).when(mNativeWrapperMock).endSession(anyLong(), eq(false)); + doAnswer(args -> { + listenerCaptor.getValue().onVibrationSessionComplete(args.getArgument(0)); + return null; + }).when(mNativeWrapperMock).endSession(anyLong(), eq(true)); + + IBinder token = mock(IBinder.class); + IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class); + doReturn(token).when(callback).asBinder(); + return callback; + } + private void cancelVibrate(VibratorManagerService service) { service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service); } @@ -3157,6 +3678,16 @@ public class VibratorManagerServiceTest { return vib; } + private VendorVibrationSession startSession(VibratorManagerService service, + VibrationAttributes attrs, IVibrationSessionCallback callback, int... vibratorIds) { + VendorVibrationSession session = service.startVendorVibrationSessionInternal(UID, + Context.DEVICE_ID_DEFAULT, PACKAGE_NAME, vibratorIds, attrs, "reason", callback); + if (session != null) { + mPendingSessions.add(session); + } + return session; + } + private boolean waitUntil(Predicate<VibratorManagerService> predicate, VibratorManagerService service, long timeout) throws InterruptedException { long timeoutTimestamp = SystemClock.uptimeMillis() + timeout; diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java index 28ae271e20fc..cf5323e1f3a5 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java @@ -288,7 +288,6 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * Sends a KEYCODE_SCREENSHOT and validates screenshot is taken if flag is enabled */ @Test - @EnableFlags(com.android.hardware.input.Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE) @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testTakeScreenshot_flagEnabled() { sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0); @@ -296,17 +295,6 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { } /** - * Sends a KEYCODE_SCREENSHOT and validates screenshot is not taken if flag is disabled - */ - @Test - @DisableFlags({com.android.hardware.input.Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE, - com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER}) - public void testTakeScreenshot_flagDisabled() { - sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0); - mPhoneWindowManager.assertTakeScreenshotNotCalled(); - } - - /** * META+CTRL+BACKSPACE for taking a bugreport when the flag is enabled. */ @Test diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java index a34094ce6452..98949d0c45cf 100644 --- a/telecomm/java/android/telecom/Log.java +++ b/telecomm/java/android/telecom/Log.java @@ -68,7 +68,7 @@ public class Log { // Used to synchronize singleton logging lazy initialization private static final Object sSingletonSync = new Object(); private static EventManager sEventManager; - private static SessionManager sSessionManager; + private static volatile SessionManager sSessionManager; private static Object sLock = null; /** @@ -379,6 +379,23 @@ public class Log { return sSessionManager; } + @VisibleForTesting + public static SessionManager setSessionManager(Context context, + java.lang.Runnable cleanSessionRunnable) { + // Checking for null again outside of synchronization because we only need to synchronize + // during the lazy loading of the session logger. We don't need to synchronize elsewhere. + if (sSessionManager == null) { + synchronized (sSingletonSync) { + if (sSessionManager == null) { + sSessionManager = new SessionManager(cleanSessionRunnable); + sSessionManager.setContext(context); + return sSessionManager; + } + } + } + return sSessionManager; + } + public static void setTag(String tag) { TAG = tag; DEBUG = isLoggable(android.util.Log.DEBUG); diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java index 00e344c67cc5..ac1e69e92ec0 100644 --- a/telecomm/java/android/telecom/Logging/SessionManager.java +++ b/telecomm/java/android/telecom/Logging/SessionManager.java @@ -62,9 +62,7 @@ public class SessionManager { @VisibleForTesting public final ConcurrentHashMap<Integer, Session> mSessionMapper = new ConcurrentHashMap<>(64); - @VisibleForTesting - public java.lang.Runnable mCleanStaleSessions = () -> - cleanupStaleSessions(getSessionCleanupTimeoutMs()); + private final java.lang.Runnable mCleanStaleSessions; private final Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper()); // Overridden in LogTest to skip query to ContentProvider @@ -110,29 +108,39 @@ public class SessionManager { } public SessionManager() { + mCleanStaleSessions = () -> cleanupStaleSessions(getSessionCleanupTimeoutMs()); + } + + @VisibleForTesting + public SessionManager(java.lang.Runnable cleanStaleSessionsRunnable) { + mCleanStaleSessions = cleanStaleSessionsRunnable; } private long getSessionCleanupTimeoutMs() { return mSessionCleanupTimeoutMs.get(); } - private synchronized void resetStaleSessionTimer() { + private void resetStaleSessionTimer() { if (!Flags.endSessionImprovements()) { - mSessionCleanupHandler.removeCallbacksAndMessages(null); - // Will be null in Log Testing - if (mCleanStaleSessions != null) { - mSessionCleanupHandler.postDelayed(mCleanStaleSessions, - getSessionCleanupTimeoutMs()); - } - } else { - if (mCleanStaleSessions != null - && !mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) { + resetStaleSessionTimerOld(); + return; + } + // Will be null in Log Testing + if (mCleanStaleSessions == null) return; + synchronized (mSessionCleanupHandler) { + if (!mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) { mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs()); } } } + private synchronized void resetStaleSessionTimerOld() { + if (mCleanStaleSessions == null) return; + mSessionCleanupHandler.removeCallbacksAndMessages(null); + mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs()); + } + /** * Determines whether or not to start a new session or continue an existing session based on * the {@link Session.Info} info passed into startSession. If info is null, a new Session is diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 2a06c3da0195..6490cbe3e31a 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2183,8 +2183,8 @@ public class CarrierConfigManager { * Maximum size in bytes of the PDU to send or download when connected to a non-terrestrial * network. MmsService will return a result code of MMS_ERROR_TOO_LARGE_FOR_TRANSPORT if * the PDU exceeds this limit when connected to a non-terrestrial network. - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT = "mms_max_ntn_payload_size_bytes_int"; @@ -9850,9 +9850,8 @@ public class CarrierConfigManager { * manually scanning available cellular network. * If key is {@code true}, satellite plmn should not be exposed to user and should be * automatically set, {@code false} otherwise. Default value is {@code true}. - * - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL = "remove_satellite_plmn_in_manual_network_scan_bool"; @@ -9877,18 +9876,18 @@ public class CarrierConfigManager { /** * Doesn't support unrestricted traffic on satellite network. - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED = 0; /** * Support unrestricted but bandwidth_constrained traffic on satellite network. - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED = 1; /** * Support unrestricted satellite network that serves all traffic. - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int SATELLITE_DATA_SUPPORT_ALL = 2; /** * Indicates what kind of traffic an {@link NetworkCapabilities#NET_CAPABILITY_NOT_RESTRICTED} @@ -9898,8 +9897,8 @@ public class CarrierConfigManager { * {@link ApnSetting#INFRASTRUCTURE_SATELLITE} from APN infrastructure_bitmask, and this * configuration is ignored. * By default it only supports restricted data. - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_SATELLITE_DATA_SUPPORT_MODE_INT = "satellite_data_support_mode_int"; @@ -9911,8 +9910,8 @@ public class CarrierConfigManager { * {@link com.android.ims.ImsConfig.WfcModeFeatureValueConstants#WIFI_PREFERRED} * {@code false} - roaming preference can be changed by user independently and is not * overridden when device is connected to non-terrestrial network. - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL = "override_wfc_roaming_mode_while_using_ntn_bool"; @@ -9945,8 +9944,8 @@ public class CarrierConfigManager { * Reference: GSMA TS.43-v11, 2.8.5 Fast Authentication and Token Management. * `app_name` is an optional attribute in the request and may vary depending on the carrier * requirement. - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING = "satellite_entitlement_app_name_string"; @@ -9954,9 +9953,8 @@ public class CarrierConfigManager { * URL to redirect user to get more information about the carrier support for satellite. * * The default value is empty string. - * - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING = "satellite_information_redirect_url_string"; /** @@ -9966,9 +9964,8 @@ public class CarrierConfigManager { * This will need agreement with carriers before enabling this flag. * * The default value is false. - * - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL = "emergency_messaging_supported_bool"; @@ -9983,9 +9980,8 @@ public class CarrierConfigManager { * prompt user to switch to using satellite emergency messaging. * * The default value is 30 seconds. - * - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT = "emergency_call_to_satellite_t911_handover_timeout_millis_int"; @@ -9998,9 +9994,8 @@ public class CarrierConfigManager { * The default capabilities are * {@link NetworkRegistrationInfo#SERVICE_TYPE_SMS}, and * {@link NetworkRegistrationInfo#SERVICE_TYPE_MMS} - * - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY = "carrier_roaming_satellite_default_services_int_array"; @@ -10031,9 +10026,8 @@ public class CarrierConfigManager { * Defines the NIDD (Non-IP Data Delivery) APN to be used for carrier roaming to satellite * attachment. For more on NIDD, see 3GPP TS 29.542. * Note this config is the only source of truth regarding the definition of the APN. - * - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_SATELLITE_NIDD_APN_NAME_STRING = "satellite_nidd_apn_name_string"; @@ -10044,9 +10038,8 @@ public class CarrierConfigManager { * * If {@code false}, the emergency call is always blocked if device is in emergency satellite * mode. Note if device is NOT in emergency satellite mode, emergency call is always allowed. - * - * @hide */ + @FlaggedApi(Flags.FLAG_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"; @@ -10059,14 +10052,14 @@ public class CarrierConfigManager { /** * Device can connect to carrier roaming non-terrestrial network automatically. - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC = 0; /** * Device can connect to carrier roaming non-terrestrial network only if user manually triggers * satellite connection. - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int CARRIER_ROAMING_NTN_CONNECT_MANUAL = 1; /** * Indicates carrier roaming non-terrestrial network connect type that the device can use to @@ -10074,8 +10067,8 @@ public class CarrierConfigManager { * If this key is set to CARRIER_ROAMING_NTN_CONNECT_MANUAL then connect button will be * displayed to user when the device is eligible to use carrier roaming * non-terrestrial network. - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT = "carrier_roaming_ntn_connect_type_int"; @@ -10088,7 +10081,6 @@ public class CarrierConfigManager { * will be made to T911. * * The default value is {@link SatelliteManager#EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911}. - * */ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) public static final String @@ -10105,9 +10097,8 @@ public class CarrierConfigManager { * After the timer is expired, device is marked as eligible for satellite communication. * * The default value is 180 seconds. - * - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT = "carrier_supported_satellite_notification_hysteresis_sec_int"; @@ -10153,7 +10144,9 @@ public class CarrierConfigManager { "satellite_roaming_esos_inactivity_timeout_sec_int"; /** - * A string array containing the list of messaging package names that support satellite. + * A string array containing the list of messaging apps that support satellite. + * + * The default value contains only "com.google.android.apps.messaging" * * @hide */ @@ -10166,9 +10159,8 @@ public class CarrierConfigManager { * the default APN (i.e. internet) will be used for tethering. * * This config is only available when using Preset APN(not user edited) as Preferred APN. - * - * @hide */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL = "disable_dun_apn_while_roaming_with_preset_apn_bool"; @@ -11313,6 +11305,8 @@ public class CarrierConfigManager { NetworkRegistrationInfo.SERVICE_TYPE_SMS, NetworkRegistrationInfo.SERVICE_TYPE_MMS }); + sDefaults.putStringArray(KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY, new String[]{ + "com.google.android.apps.messaging"}); sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false); sDefaults.putBoolean(KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, false); sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT, diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 7be3f337e43a..88dddcfc9180 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -483,43 +483,43 @@ public final class SatelliteManager { /** * Telephony framework needs to access the current location of the device to perform the * request. However, location in the settings is disabled by users. - * * @hide */ - @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int SATELLITE_RESULT_LOCATION_DISABLED = 25; /** * Telephony framework needs to access the current location of the device to perform the * request. However, Telephony fails to fetch the current location from location service. - * * @hide */ - @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int SATELLITE_RESULT_LOCATION_NOT_AVAILABLE = 26; /** * Emergency call is in progress. - * * @hide */ - @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS = 27; /** * Disabling satellite is in progress. - * * @hide */ - @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int SATELLITE_RESULT_DISABLE_IN_PROGRESS = 28; /** * Enabling satellite is in progress. - * * @hide */ - @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int SATELLITE_RESULT_ENABLE_IN_PROGRESS = 29; /** @hide */ @@ -715,7 +715,7 @@ public final class SatelliteManager { public static final int EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911 = 2; /** - * This intent will be broadcasted if there are any change to list of subscriber informations. + * This intent will be broadcasted if there are any change to list of subscriber information. * This intent will be sent only to the app with component defined in * config_satellite_carrier_roaming_esos_provisioned_class and package defined in * config_satellite_gateway_service_package @@ -1349,12 +1349,16 @@ public final class SatelliteManager { * The satellite modem is being powered on. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int SATELLITE_MODEM_STATE_ENABLING_SATELLITE = 8; /** * The satellite modem is being powered off. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int SATELLITE_MODEM_STATE_DISABLING_SATELLITE = 9; /** @@ -1414,6 +1418,8 @@ public final class SatelliteManager { * there is any incoming message. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3; /** @@ -1421,6 +1427,8 @@ public final class SatelliteManager { * is the last message to emergency service provider indicating still needs help. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP = 4; /** @@ -1428,12 +1436,16 @@ public final class SatelliteManager { * is the last message to emergency service provider indicating no more help is needed. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5; /** * Datagram type indicating that the message to be sent or received is of type SMS. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int DATAGRAM_TYPE_SMS = 6; /** @@ -1441,6 +1453,8 @@ public final class SatelliteManager { * for pending incoming SMS. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS = 7; /** @hide */ @@ -1461,6 +1475,8 @@ public final class SatelliteManager { * Satellite communication restricted by user. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER = 0; /** diff --git a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt index 862886ce69d2..e281a3fb1287 100644 --- a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt +++ b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt @@ -61,13 +61,13 @@ class InputGestureManagerTests { assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS, result) assertEquals( listOf(customGesture), - inputGestureManager.getCustomInputGestures(USER_ID) + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) ) inputGestureManager.removeCustomInputGesture(USER_ID, customGesture) assertEquals( listOf<InputGestureData>(), - inputGestureManager.getCustomInputGestures(USER_ID) + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) ) } @@ -86,7 +86,7 @@ class InputGestureManagerTests { assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST, result) assertEquals( listOf<InputGestureData>(), - inputGestureManager.getCustomInputGestures(USER_ID) + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) ) } @@ -115,7 +115,7 @@ class InputGestureManagerTests { assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS, result) assertEquals( listOf(customGesture), - inputGestureManager.getCustomInputGestures(USER_ID) + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) ) } @@ -144,13 +144,67 @@ class InputGestureManagerTests { assertEquals( listOf(customGesture, customGesture2), - inputGestureManager.getCustomInputGestures(USER_ID) + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) ) - inputGestureManager.removeAllCustomInputGestures(USER_ID) + inputGestureManager.removeAllCustomInputGestures(USER_ID, /* filter = */null) assertEquals( listOf<InputGestureData>(), - inputGestureManager.getCustomInputGestures(USER_ID) + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) + ) + } + + @Test + fun filteringBasedOnTouchpadOrKeyGestures() { + val customKeyGesture = InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_H, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .build() + inputGestureManager.addCustomInputGesture(USER_ID, customKeyGesture) + val customTouchpadGesture = InputGestureData.Builder() + .setTrigger( + InputGestureData.createTouchpadTrigger( + InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK) + .build() + inputGestureManager.addCustomInputGesture(USER_ID, customTouchpadGesture) + + assertEquals( + listOf(customTouchpadGesture, customKeyGesture), + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) + ) + assertEquals( + listOf(customKeyGesture), + inputGestureManager.getCustomInputGestures(USER_ID, InputGestureData.Filter.KEY) + ) + assertEquals( + listOf(customTouchpadGesture), + inputGestureManager.getCustomInputGestures( + USER_ID, + InputGestureData.Filter.TOUCHPAD + ) + ) + + inputGestureManager.removeAllCustomInputGestures(USER_ID, InputGestureData.Filter.KEY) + assertEquals( + listOf(customTouchpadGesture), + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) + ) + + inputGestureManager.removeAllCustomInputGestures( + USER_ID, + InputGestureData.Filter.TOUCHPAD + ) + assertEquals( + listOf<InputGestureData>(), + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) ) } }
\ No newline at end of file diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index 1574d1b7ce6f..f317939c96c4 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -995,11 +995,28 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR) ), + TestData( + "LOCK -> Lock Screen", + intArrayOf(KeyEvent.KEYCODE_LOCK), + KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN, + intArrayOf(KeyEvent.KEYCODE_LOCK), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "FULLSCREEN -> Maximizes a task to fit the screen", + intArrayOf(KeyEvent.KEYCODE_FULLSCREEN), + KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW, + intArrayOf(KeyEvent.KEYCODE_FULLSCREEN), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), ) } @Test @Parameters(method = "systemKeysTestArguments") + @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) fun testSystemKeys(test: TestData) { setupKeyGestureController() testKeyGestureInternal(test) @@ -1029,6 +1046,9 @@ class KeyGestureControllerTests { KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY, KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY, KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL, + KeyEvent.KEYCODE_DO_NOT_DISTURB, + KeyEvent.KEYCODE_LOCK, + KeyEvent.KEYCODE_FULLSCREEN ) val handler = KeyGestureHandler { _, _ -> false } diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java index 07b733830bd3..0da4521fca71 100644 --- a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java +++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java @@ -143,6 +143,38 @@ public class VibratorManagerServicePermissionTest { } @Test + public void testStartVendorVibrationSessionWithoutVibratePermissionFails() throws Exception { + getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( + Manifest.permission.VIBRATE_VENDOR_EFFECTS, + Manifest.permission.START_VIBRATION_SESSIONS); + expectSecurityException("VIBRATE"); + mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME, + new int[] { 1 }, ATTRS, "testVibrate", null); + } + + @Test + public void testStartVendorVibrationSessionWithoutVibrateVendorEffectsPermissionFails() + throws Exception { + getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( + Manifest.permission.VIBRATE, + Manifest.permission.START_VIBRATION_SESSIONS); + expectSecurityException("VIBRATE"); + mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME, + new int[] { 1 }, ATTRS, "testVibrate", null); + } + + @Test + public void testStartVendorVibrationSessionWithoutStartSessionPermissionFails() + throws Exception { + getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( + Manifest.permission.VIBRATE, + Manifest.permission.VIBRATE_VENDOR_EFFECTS); + expectSecurityException("VIBRATE"); + mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME, + new int[] { 1 }, ATTRS, "testVibrate", null); + } + + @Test public void testCancelVibrateFails() throws RemoteException { expectSecurityException("VIBRATE"); mVibratorService.cancelVibrate(/* usageFilter= */ -1, new Binder()); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java index 49665f7a3304..613b92616707 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java @@ -360,12 +360,10 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { private void verifyGetSafeModeTimeoutMs( boolean isInTestMode, - boolean isConfigTimeoutSupported, PersistableBundleWrapper carrierConfig, long expectedTimeoutMs) throws Exception { doReturn(isInTestMode).when(mVcnContext).isInTestMode(); - doReturn(isConfigTimeoutSupported).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled(); final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class); doReturn(carrierConfig).when(snapshot).getCarrierConfigForSubGrp(TEST_SUB_GRP); @@ -377,16 +375,7 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { } @Test - public void testGetSafeModeTimeoutMs_configTimeoutUnsupported() throws Exception { - verifyGetSafeModeTimeoutMs( - false /* isInTestMode */, - false /* isConfigTimeoutSupported */, - null /* carrierConfig */, - TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS)); - } - - @Test - public void testGetSafeModeTimeoutMs_configTimeoutSupported() throws Exception { + public void testGetSafeModeTimeoutMs() throws Exception { final int carrierConfigTimeoutSeconds = 20; final PersistableBundleWrapper carrierConfig = mock(PersistableBundleWrapper.class); doReturn(carrierConfigTimeoutSeconds) @@ -395,17 +384,14 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { verifyGetSafeModeTimeoutMs( false /* isInTestMode */, - true /* isConfigTimeoutSupported */, carrierConfig, TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds)); } @Test - public void testGetSafeModeTimeoutMs_configTimeoutSupported_carrierConfigNull() - throws Exception { + public void testGetSafeModeTimeoutMs_carrierConfigNull() throws Exception { verifyGetSafeModeTimeoutMs( false /* isInTestMode */, - true /* isConfigTimeoutSupported */, null /* carrierConfig */, TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS)); } @@ -420,7 +406,6 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { verifyGetSafeModeTimeoutMs( true /* isInTestMode */, - true /* isConfigTimeoutSupported */, carrierConfig, TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds)); } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 4c7b25aaa7c3..8374fd944568 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -222,7 +222,6 @@ public class VcnGatewayConnectionTestBase { doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper(); doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider(); doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags(); - doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled(); doReturn(mUnderlyingNetworkController) .when(mDeps) diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt index 1abe77fd3ceb..f260e2733843 100644 --- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt +++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt @@ -188,7 +188,7 @@ object SystemFeaturesGenerator { ?: throw IllegalArgumentException( "Invalid feature version input for $name: ${featureArgs[1]}" ) - FeatureInfo(name, featureArgs[1].toInt(), readonly = true) + FeatureInfo(name, featureVersion, readonly = true) } } } |