diff options
83 files changed, 2269 insertions, 1158 deletions
diff --git a/api/Android.bp b/api/Android.bp index 0ac85e28de1a..cdc5cd120956 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) " + @@ -194,7 +230,7 @@ java_genrule { cmd: "$(location merge_zips) $(out) $(in)", srcs: [ ":api-stubs-docs-non-updatable{.exportable}", - ":all-modules-public-stubs-source", + ":all-modules-public-stubs-source-exportable", ], visibility: ["//visibility:private"], // Used by make module in //development, mind } diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp index e8fcf4b2b32d..1ebe0cdfabd7 100644 --- a/api/ApiDocs.bp +++ b/api/ApiDocs.bp @@ -129,7 +129,7 @@ droidstubs { droidstubs { name: "framework-doc-stubs", defaults: ["android-non-updatable-doc-stubs-defaults"], - srcs: [":all-modules-public-stubs-source"], + srcs: [":all-modules-public-stubs-source-exportable"], api_levels_module: "api_versions_public", aidl: { include_dirs: [ diff --git a/api/api.go b/api/api.go index f32bdc32f75d..5ca24de1b46a 100644 --- a/api/api.go +++ b/api/api.go @@ -429,8 +429,9 @@ func createMergedFrameworkSystemServerExportableStubs(ctx android.LoadHookContex func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) { props := fgProps{} - props.Name = proptools.StringPtr("all-modules-public-stubs-source") - props.Device_common_srcs = createSrcs(modules, "{.public.stubs.source}") + props.Name = proptools.StringPtr("all-modules-public-stubs-source-exportable") + transformConfigurableArray(modules, "", ".stubs.source") + props.Device_common_srcs = createSrcs(modules, "{.exportable}") props.Visibility = []string{"//frameworks/base"} ctx.CreateModule(android.FileGroupFactory, &props) } diff --git a/core/api/current.txt b/core/api/current.txt index 616f6a7a90d9..1edb193642c8 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -478,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 @@ -8790,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>); @@ -8803,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"; } @@ -8825,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 = "android_app_appfunctions_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 } } @@ -8871,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 { @@ -51994,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 @@ -52006,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 @@ -52018,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 @@ -52030,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 @@ -52045,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 @@ -52068,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 @@ -52105,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 @@ -52139,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 @@ -52151,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/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/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/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index fee071b14016..be24bfa41e10 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -367,3 +367,10 @@ flag { description: "Allows DPMS to enable or disable SupervisionService based on whether the device is being managed by the supervision role holder." bug: "376213673" } + +flag { + name: "split_create_managed_profile_enabled" + namespace: "enterprise" + description: "Split up existing create and provision managed profile API." + bug: "375382324" +} 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/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java index ced4b553d641..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 @@ -74,112 +68,6 @@ public final class ExecuteAppFunctionResponse implements Parcelable { public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_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 @@ -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/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/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/security/flags.aconfig b/core/java/android/security/flags.aconfig index 1d35344e5218..7cb0ffcfcc72 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -120,3 +120,10 @@ flag { description: "Feature flag for exposing KeyStore grant APIs" bug: "351158708" } + +flag { + name: "secure_lockdown" + namespace: "biometrics" + description: "Feature flag for Secure Lockdown feature" + bug: "373422357" +}
\ No newline at end of file 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/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/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/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/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index ea783e9cadb6..3caad0966b1f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_PIP; @@ -230,6 +231,11 @@ public class PipTransition extends PipTransitionController implements // If there is no PiP change, exit this transition handler and potentially try others. if (pipChange == null) return false; + // Other targets might have default transforms applied that are not relevant when + // playing PiP transitions, so reset those transforms if needed. + prepareOtherTargetTransforms(info, startTransaction, finishTransaction); + + // Update the PipTransitionState while supplying the PiP leash and token to be cached. Bundle extra = new Bundle(); extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer()); extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash()); @@ -341,17 +347,21 @@ public class PipTransition extends PipTransitionController implements (destinationBounds.height() - overlaySize) / 2f); } - final int startRotation = pipChange.getStartRotation(); - final int endRotation = mPipDisplayLayoutState.getRotation(); - final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 - : startRotation - endRotation; + final int delta = getFixedRotationDelta(info, pipChange); if (delta != ROTATION_0) { - mPipTransitionState.setInFixedRotation(true); - handleBoundsEnterFixedRotation(pipChange, pipActivityChange, endRotation); + // Update transition target changes in place to prepare for fixed rotation. + handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange); } + // Update the src-rect-hint in params in place, to set up initial animator transform. + Rect sourceRectHint = getAdjustedSourceRectHint(info, pipChange, pipActivityChange); + pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint().set(sourceRectHint); + + // Config-at-end transitions need to have their activities transformed before starting + // the animation; this makes the buffer seem like it's been updated to final size. prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange, pipActivityChange); + startTransaction.merge(finishTransaction); PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash, startTransaction, finishTransaction, destinationBounds, delta); @@ -387,55 +397,36 @@ public class PipTransition extends PipTransitionController implements return false; } + final SurfaceControl pipLeash = getLeash(pipChange); final Rect startBounds = pipChange.getStartAbsBounds(); final Rect endBounds = pipChange.getEndAbsBounds(); - final PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams; - final float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params); - final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds, - endBounds); - final Rect adjustedSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) - : PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio); - - final SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); - - // For opening type transitions, if there is a change of mode TO_FRONT/OPEN, - // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f - // by the Transitions framework to simplify Task opening transitions. - if (TransitionUtil.isOpeningType(info.getType())) { - for (TransitionInfo.Change change : info.getChanges()) { - if (change.getLeash() == null) continue; - if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) { - startTransaction.setAlpha(change.getLeash(), 1f); - } - } - } - - final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); - final int startRotation = pipChange.getStartRotation(); - final int endRotation = fixedRotationChange != null - ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; - final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 - : startRotation - endRotation; + final Rect adjustedSourceRectHint = getAdjustedSourceRectHint(info, pipChange, + pipActivityChange); + final int delta = getFixedRotationDelta(info, pipChange); if (delta != ROTATION_0) { - mPipTransitionState.setInFixedRotation(true); - handleBoundsEnterFixedRotation(pipChange, pipActivityChange, - fixedRotationChange.getEndFixedRotation()); + // Update transition target changes in place to prepare for fixed rotation. + handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange); } PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash, startTransaction, finishTransaction, endBounds, delta); - if (sourceRectHint == null) { - // update the src-rect-hint in params in place, to set up initial animator transform. - params.getSourceRectHint().set(adjustedSourceRectHint); + if (PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds, endBounds) == null) { + // If app provided src-rect-hint is invalid, use app icon overlay. animator.setAppIconContentOverlay( mContext, startBounds, endBounds, pipChange.getTaskInfo().topActivityInfo, mPipBoundsState.getLauncherState().getAppIconSizePx()); } + // Update the src-rect-hint in params in place, to set up initial animator transform. + params.getSourceRectHint().set(adjustedSourceRectHint); + + // Config-at-end transitions need to have their activities transformed before starting + // the animation; this makes the buffer seem like it's been updated to final size. prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange, pipActivityChange); + animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange)); animator.setAnimationEndCallback(() -> { if (animator.getContentOverlayLeash() != null) { @@ -457,11 +448,22 @@ public class PipTransition extends PipTransitionController implements animator.start(); } - private void handleBoundsEnterFixedRotation(TransitionInfo.Change pipTaskChange, - TransitionInfo.Change pipActivityChange, int endRotation) { - final Rect endBounds = pipTaskChange.getEndAbsBounds(); - final Rect endActivityBounds = pipActivityChange.getEndAbsBounds(); - int startRotation = pipTaskChange.getStartRotation(); + private void handleBoundsEnterFixedRotation(TransitionInfo info, + TransitionInfo.Change outPipTaskChange, + TransitionInfo.Change outPipActivityChange) { + final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); + final Rect endBounds = outPipTaskChange.getEndAbsBounds(); + final Rect endActivityBounds = outPipActivityChange.getEndAbsBounds(); + int startRotation = outPipTaskChange.getStartRotation(); + int endRotation = fixedRotationChange != null + ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation(); + + if (startRotation == endRotation) { + return; + } + + // This is used by display change listeners to respond properly to fixed rotation. + mPipTransitionState.setInFixedRotation(true); // Cache the task to activity offset to potentially restore later. Point activityEndOffset = new Point(endActivityBounds.left - endBounds.left, @@ -490,15 +492,15 @@ public class PipTransition extends PipTransitionController implements endBounds.top + activityEndOffset.y); } - private void handleExpandFixedRotation(TransitionInfo.Change pipTaskChange, int endRotation) { - final Rect endBounds = pipTaskChange.getEndAbsBounds(); + private void handleExpandFixedRotation(TransitionInfo.Change outPipTaskChange, int delta) { + final Rect endBounds = outPipTaskChange.getEndAbsBounds(); final int width = endBounds.width(); final int height = endBounds.height(); final int left = endBounds.left; final int top = endBounds.top; int newTop, newLeft; - if (endRotation == Surface.ROTATION_90) { + if (delta == Surface.ROTATION_90) { newLeft = top; newTop = -(left + width); } else { @@ -585,15 +587,11 @@ public class PipTransition extends PipTransitionController implements final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds, startBounds); - final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); - final int startRotation = pipChange.getStartRotation(); - final int endRotation = fixedRotationChange != null - ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; - final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 - : endRotation - startRotation; - + // We define delta = startRotation - endRotation, so we need to flip the sign. + final int delta = -getFixedRotationDelta(info, pipChange); if (delta != ROTATION_0) { - handleExpandFixedRotation(pipChange, endRotation); + // Update PiP target change in place to prepare for fixed rotation; + handleExpandFixedRotation(pipChange, delta); } PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash, @@ -661,6 +659,72 @@ public class PipTransition extends PipTransitionController implements return null; } + @NonNull + private Rect getAdjustedSourceRectHint(@NonNull TransitionInfo info, + @NonNull TransitionInfo.Change pipTaskChange, + @NonNull TransitionInfo.Change pipActivityChange) { + final Rect startBounds = pipTaskChange.getStartAbsBounds(); + final Rect endBounds = pipTaskChange.getEndAbsBounds(); + final PictureInPictureParams params = pipTaskChange.getTaskInfo().pictureInPictureParams; + + // Get the source-rect-hint provided by the app and check its validity; null if invalid. + final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds, + endBounds); + + final Rect adjustedSourceRectHint = new Rect(); + if (sourceRectHint != null) { + adjustedSourceRectHint.set(sourceRectHint); + // If multi-activity PiP, use the parent task before PiP to retrieve display cutouts; + // then, offset the valid app provided source rect hint by the cutout insets. + // For single-activity PiP, just use the pinned task to get the cutouts instead. + TransitionInfo.Change parentBeforePip = pipActivityChange.getLastParent() != null + ? getChangeByToken(info, pipActivityChange.getLastParent()) : null; + Rect cutoutInsets = parentBeforePip != null + ? parentBeforePip.getTaskInfo().displayCutoutInsets + : pipTaskChange.getTaskInfo().displayCutoutInsets; + if (cutoutInsets != null + && getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) { + adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top); + } + } else { + // For non-valid app provided src-rect-hint, calculate one to crop into during + // app icon overlay animation. + float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params); + adjustedSourceRectHint.set( + PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio)); + } + return adjustedSourceRectHint; + } + + @Surface.Rotation + private int getFixedRotationDelta(@NonNull TransitionInfo info, + @NonNull TransitionInfo.Change pipChange) { + TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); + int startRotation = pipChange.getStartRotation(); + int endRotation = fixedRotationChange != null + ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation(); + int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 + : startRotation - endRotation; + return delta; + } + + private void prepareOtherTargetTransforms(TransitionInfo info, + SurfaceControl.Transaction startTransaction, + SurfaceControl.Transaction finishTransaction) { + // For opening type transitions, if there is a change of mode TO_FRONT/OPEN, + // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f + // by the Transitions framework to simplify Task opening transitions. + if (TransitionUtil.isOpeningType(info.getType())) { + for (TransitionInfo.Change change : info.getChanges()) { + if (change.getLeash() == null) continue; + if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) { + startTransaction.setAlpha(change.getLeash(), 1f); + } + } + } + + } + private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { // cache the original task token to check for multi-activity case later diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index 0eda10112ee8..f56667df92ca 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -1,9 +1,29 @@ // Signature format: 2.0 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.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.android.extensions.appfunctions.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.android.extensions.appfunctions { 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.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.android.extensions.appfunctions.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"; } @@ -35,27 +55,11 @@ package com.android.extensions.appfunctions { } 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.android.extensions.appfunctions.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle); - method @NonNull public static com.android.extensions.appfunctions.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 = "android_app_appfunctions_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 } } 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/android/extensions/appfunctions/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java index d64593d8ff5f..9eb66a33fedc 100644 --- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java @@ -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/android/extensions/appfunctions/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java index 1a4d9da8bd63..55f579138218 100644 --- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java @@ -16,7 +16,8 @@ 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,8 +27,7 @@ import android.content.Intent; import android.os.Binder; import android.os.CancellationSignal; import android.os.IBinder; - -import java.util.function.Consumer; +import android.os.OutcomeReceiver; /** * Abstract base class to provide app functions to the system. @@ -79,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)); + } }); }); @@ -115,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/android/extensions/appfunctions/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java index 7c5ddcd9edfb..42c3c033ad38 100644 --- a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java @@ -16,23 +16,14 @@ package com.android.extensions.appfunctions; -import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; +import android.app.appfunctions.AppFunctionManager; 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). - */ +/** 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 @@ -51,112 +42,6 @@ public final class ExecuteAppFunctionResponse { public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_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 @@ -169,99 +54,21 @@ public final class ExecuteAppFunctionResponse { /** 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); + public ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument) { + this(resultDocument, Bundle.EMPTY); } /** - * 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. + * @param resultDocument The return value of the executed function. + * @param extras The additional metadata for this function execution response. */ - @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) { + mResultDocument = Objects.requireNonNull(resultDocument); + mExtras = Objects.requireNonNull(extras); } /** @@ -269,9 +76,6 @@ public final class ExecuteAppFunctionResponse { * * <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> @@ -283,77 +87,17 @@ public final class ExecuteAppFunctionResponse { * // Do something with the returnValue * } * </pre> + * + * @see AppFunctionManager on how to determine the expected function return. */ @NonNull public GenericDocument getResultDocument() { return mResultDocument; } - /** Returns the extras of the app function execution. */ + /** Returns the additional metadata for this function execution response. */ @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/java/com/android/extensions/appfunctions/SidecarConverter.java b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java index 56f2725fccc7..5e1fc7e684e2 100644 --- a/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java @@ -52,13 +52,21 @@ public final class SidecarConverter { @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 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()); } /** @@ -86,12 +94,19 @@ public final class SidecarConverter { @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/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt index 6118e6ce4ab2..11202d58e484 100644 --- a/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt +++ b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt @@ -16,6 +16,7 @@ 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 @@ -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 @@ -130,46 +125,38 @@ class SidecarConverterTest { .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true) .build() val sidecarResponse = - com.android.extensions.appfunctions.ExecuteAppFunctionResponse.newSuccess( - resultGd, - null - ) + 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.android.extensions.appfunctions.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/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/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/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/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index b4d81d6937ed..7c478ac78a29 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -353,7 +353,7 @@ public class BugreportProgressService extends Service { public void onDestroy() { mServiceHandler.getLooper().quit(); mScreenshotHandler.getLooper().quit(); - mBugreportSingleThreadExecutor.close(); + mBugreportSingleThreadExecutor.shutdown(); super.onDestroy(); } 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 eeab232542c0..163f4b36f472 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 @@ -59,6 +59,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.hideFromAccessibility import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset @@ -286,7 +287,10 @@ private fun DragHandle(dialog: Dialog) { Surface( modifier = Modifier.padding(top = 16.dp, bottom = 6.dp) - .semantics { contentDescription = dragHandleContentDescription } + .semantics { + contentDescription = dragHandleContentDescription + hideFromAccessibility() + } .clickable { dialog.dismiss() }, color = MaterialTheme.colorScheme.onSurfaceVariant, shape = MaterialTheme.shapes.extraLarge, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index e7b66c5f0d2f..d976e8e62f3c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -693,8 +693,8 @@ private fun prepareInterruption( val fromState = updateStateInContent(transition.fromContent) val toState = updateStateInContent(transition.toContent) - reconcileStates(element, previousTransition) - reconcileStates(element, transition) + val previousUniqueState = reconcileStates(element, previousTransition, previousState = null) + reconcileStates(element, transition, previousState = previousUniqueState) // Remove the interruption values to all contents but the content(s) where the element will be // placed, to make sure that interruption deltas are computed only right after this interruption @@ -721,12 +721,32 @@ private fun prepareInterruption( /** * Reconcile the state of [element] in the formContent and toContent of [transition] so that the * values before interruption have their expected values, taking shared transitions into account. + * + * @return the unique state this element had during [transition], `null` if it had multiple + * different states (i.e. the shared animation was disabled). */ -private fun reconcileStates(element: Element, transition: TransitionState.Transition) { - val fromContentState = element.stateByContent[transition.fromContent] ?: return - val toContentState = element.stateByContent[transition.toContent] ?: return +private fun reconcileStates( + element: Element, + transition: TransitionState.Transition, + previousState: Element.State?, +): Element.State? { + fun reconcileWithPreviousState(state: Element.State) { + if (previousState != null && state.offsetBeforeInterruption == Offset.Unspecified) { + state.updateValuesBeforeInterruption(previousState) + } + } + + val fromContentState = element.stateByContent[transition.fromContent] + val toContentState = element.stateByContent[transition.toContent] + + if (fromContentState == null || toContentState == null) { + return (fromContentState ?: toContentState) + ?.also { reconcileWithPreviousState(it) } + ?.takeIf { it.offsetBeforeInterruption != Offset.Unspecified } + } + if (!isSharedElementEnabled(element.key, transition)) { - return + return null } if ( @@ -735,13 +755,19 @@ private fun reconcileStates(element: Element, transition: TransitionState.Transi ) { // Element is shared and placed in fromContent only. toContentState.updateValuesBeforeInterruption(fromContentState) - } else if ( + return fromContentState + } + + if ( toContentState.offsetBeforeInterruption != Offset.Unspecified && fromContentState.offsetBeforeInterruption == Offset.Unspecified ) { // Element is shared and placed in toContent only. fromContentState.updateValuesBeforeInterruption(toContentState) + return toContentState } + + return null } private fun Element.State.selfUpdateValuesBeforeInterruption() { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 4a9051598ddc..a301856d024f 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -2638,4 +2638,58 @@ class ElementTest { assertWithMessage("Frame $i didn't replace Foo").that(numberOfPlacements).isEqualTo(0) } } + + @Test + fun interruption_considerPreviousUniqueState() { + @Composable + fun SceneScope.Foo(modifier: Modifier = Modifier) { + Box(modifier.element(TestElements.Foo).size(50.dp)) + } + + val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) } + val scope = + rule.setContentAndCreateMainScope { + SceneTransitionLayout(state) { + scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } } + scene(SceneB) { Box(Modifier.fillMaxSize()) } + scene(SceneC) { + Box(Modifier.fillMaxSize()) { Foo(Modifier.offset(x = 100.dp, y = 100.dp)) } + } + } + } + + // During A => B, Foo disappears and stays in its original position. + scope.launch { state.startTransition(transition(SceneA, SceneB)) } + rule + .onNode(isElement(TestElements.Foo)) + .assertSizeIsEqualTo(50.dp) + .assertPositionInRootIsEqualTo(0.dp, 0.dp) + + // Interrupt A => B by B => C. + var interruptionProgress by mutableFloatStateOf(1f) + scope.launch { + state.startTransition( + transition(SceneB, SceneC, interruptionProgress = { interruptionProgress }) + ) + } + + // During B => C, Foo appears again. It is still at (0, 0) when the interruption progress is + // 100%, and converges to its position (100, 100) in C. + rule + .onNode(isElement(TestElements.Foo)) + .assertSizeIsEqualTo(50.dp) + .assertPositionInRootIsEqualTo(0.dp, 0.dp) + + interruptionProgress = 0.5f + rule + .onNode(isElement(TestElements.Foo)) + .assertSizeIsEqualTo(50.dp) + .assertPositionInRootIsEqualTo(50.dp, 50.dp) + + interruptionProgress = 0f + rule + .onNode(isElement(TestElements.Foo)) + .assertSizeIsEqualTo(50.dp) + .assertPositionInRootIsEqualTo(100.dp, 100.dp) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt index 160865d625f5..81d3f7232c78 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt @@ -31,11 +31,8 @@ import com.android.systemui.flags.fakeSystemPropertiesHelper import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository -import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeTrustRepository -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.AuthenticationFlags -import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest @@ -243,16 +240,11 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { } @Test - fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleepInAod() = + fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleep() = testScope.runTest { val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) assertThat(deviceUnlockStatus?.isUnlocked).isFalse() - kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - testScope = this, - ) kosmos.powerInteractor.setAsleepForTest() runCurrent() @@ -266,26 +258,6 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { } @Test - fun deviceUnlockStatus_staysLocked_whenFingerprintUnlocked_whileDeviceAsleep() = - testScope.runTest { - val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) - assertThat(deviceUnlockStatus?.isUnlocked).isFalse() - assertThat(kosmos.keyguardTransitionInteractor.getCurrentState()) - .isEqualTo(KeyguardState.LOCKSCREEN) - - kosmos.powerInteractor.setAsleepForTest() - runCurrent() - - assertThat(deviceUnlockStatus?.isUnlocked).isFalse() - - kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) - runCurrent() - assertThat(deviceUnlockStatus?.isUnlocked).isFalse() - } - - @Test fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() = testScope.runTest { kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 55f88cc5b7a2..08b996146f2c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -753,7 +753,7 @@ class SceneContainerStartableTest : SysuiTestCase() { lastSleepReason = WakeSleepReason.POWER_BUTTON, powerButtonLaunchGestureTriggered = false, ) - transitionStateFlow.value = Transition(from = Scenes.Shade, to = Scenes.Lockscreen) + transitionStateFlow.value = Transition(from = Scenes.Gone, to = Scenes.Lockscreen) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) kosmos.fakePowerRepository.updateWakefulness( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt new file mode 100644 index 000000000000..a9a5cac6112e --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt @@ -0,0 +1,81 @@ +/* + * 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.shade.data.repository + +import android.view.Display +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.statusbar.commandline.commandRegistry +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ShadePositionRepositoryTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val commandRegistry = kosmos.commandRegistry + private val pw = PrintWriter(StringWriter()) + + private val underTest = ShadePositionRepositoryImpl(commandRegistry) + + @Before + fun setUp() { + underTest.start() + } + + @Test + fun commandDisplayOverride_updatesDisplayId() = + testScope.runTest { + val displayId by collectLastValue(underTest.displayId) + assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY) + + val newDisplayId = 2 + commandRegistry.onShellCommand( + pw, + arrayOf("shade_display_override", newDisplayId.toString()), + ) + + assertThat(displayId).isEqualTo(newDisplayId) + } + + @Test + fun commandShadeDisplayOverride_resetsDisplayId() = + testScope.runTest { + val displayId by collectLastValue(underTest.displayId) + assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY) + + val newDisplayId = 2 + commandRegistry.onShellCommand( + pw, + arrayOf("shade_display_override", newDisplayId.toString()), + ) + assertThat(displayId).isEqualTo(newDisplayId) + + commandRegistry.onShellCommand(pw, arrayOf("shade_display_override", "reset")) + assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY) + } +} 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 9b47eaddffd6..8a4593032748 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 @@ -30,6 +30,7 @@ import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.testKosmos import com.android.systemui.user.data.model.SelectedUserModel @@ -74,6 +75,10 @@ class UserRepositoryImplTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) tracker = FakeUserTracker() + context.orCreateTestableResources.addOverride( + R.bool.config_userSwitchingMustGoThroughLoginScreen, + false, + ) } @Test diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt index c464a66ea0c3..6c335e71cfde 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt @@ -18,13 +18,18 @@ package com.android.systemui.deviceentry import com.android.keyguard.EmptyLockIconViewController import com.android.keyguard.LockIconViewController +import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule +import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import dagger.Binds import dagger.Lazy import dagger.Module import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import dagger.multibindings.Multibinds @Module(includes = [DeviceEntryRepositoryModule::class, FaceWakeUpTriggersConfigModule::class]) @@ -34,6 +39,13 @@ abstract class DeviceEntryModule { */ @Multibinds abstract fun deviceEntryIconTransitionSet(): Set<DeviceEntryIconTransition> + @Binds + @IntoMap + @ClassKey(DeviceUnlockedInteractor.Activator::class) + abstract fun deviceUnlockedInteractorActivator( + activator: DeviceUnlockedInteractor.Activator + ): CoreStartable + companion object { @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt index 3f937bba46d4..675f00a89d23 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt @@ -5,6 +5,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.user.data.repository.UserRepository import dagger.Binds @@ -42,6 +43,8 @@ interface DeviceEntryRepository { */ val isBypassEnabled: StateFlow<Boolean> + val deviceUnlockStatus: MutableStateFlow<DeviceUnlockStatus> + /** * Whether the lockscreen is enabled for the current user. This is `true` whenever the user has * chosen any secure authentication method and even if they set the lockscreen to be dismissed @@ -84,6 +87,9 @@ constructor( initialValue = keyguardBypassController.bypassEnabled, ) + override val deviceUnlockStatus = + MutableStateFlow(DeviceUnlockStatus(isUnlocked = false, deviceUnlockSource = null)) + override suspend fun isLockscreenEnabled(): Boolean { return withContext(backgroundDispatcher) { val selectedUserId = userRepository.getSelectedUserInfo().id diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt index 5259c5dca39f..24278ecc76bd 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt @@ -16,7 +16,9 @@ package com.android.systemui.deviceentry.domain.interactor +import android.util.Log import androidx.annotation.VisibleForTesting +import com.android.systemui.CoreStartable import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.dagger.SysUISingleton @@ -26,42 +28,40 @@ import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReaso import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus import com.android.systemui.flags.SystemPropertiesHelper -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.TrustInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class DeviceUnlockedInteractor @Inject constructor( - @Application private val applicationScope: CoroutineScope, - authenticationInteractor: AuthenticationInteractor, - deviceEntryRepository: DeviceEntryRepository, + private val authenticationInteractor: AuthenticationInteractor, + private val repository: DeviceEntryRepository, trustInteractor: TrustInteractor, faceAuthInteractor: DeviceEntryFaceAuthInteractor, fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, private val powerInteractor: PowerInteractor, private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor, private val systemPropertiesHelper: SystemPropertiesHelper, - keyguardTransitionInteractor: KeyguardTransitionInteractor, -) { +) : ExclusiveActivatable() { private val deviceUnlockSource = merge( @@ -69,7 +69,7 @@ constructor( faceAuthInteractor.isAuthenticated .filter { it } .map { - if (deviceEntryRepository.isBypassEnabled.value) { + if (repository.isBypassEnabled.value) { DeviceUnlockSource.FaceWithBypass } else { DeviceUnlockSource.FaceWithoutBypass @@ -163,43 +163,59 @@ constructor( * proceed. */ val deviceUnlockStatus: StateFlow<DeviceUnlockStatus> = - authenticationInteractor.authenticationMethod - .flatMapLatest { authMethod -> - if (!authMethod.isSecure) { - flowOf(DeviceUnlockStatus(true, null)) - } else if (authMethod == AuthenticationMethodModel.Sim) { - // Device is locked if SIM is locked. - flowOf(DeviceUnlockStatus(false, null)) - } else { - combine( - powerInteractor.isAsleep, - isInLockdown, - keyguardTransitionInteractor - .transitionValue(KeyguardState.AOD) - .map { it == 1f } - .distinctUntilChanged(), - ::Triple, - ) - .flatMapLatestConflated { (isAsleep, isInLockdown, isAod) -> - val isForceLocked = - when { - isAsleep && !isAod -> true - isInLockdown -> true - else -> false - } - if (isForceLocked) { - flowOf(DeviceUnlockStatus(false, null)) - } else { - deviceUnlockSource.map { DeviceUnlockStatus(true, it) } + repository.deviceUnlockStatus.asStateFlow() + + override suspend fun onActivated(): Nothing { + authenticationInteractor.authenticationMethod.collectLatest { authMethod -> + if (!authMethod.isSecure) { + // Device remains unlocked as long as the authentication method is not secure. + Log.d(TAG, "remaining unlocked because auth method not secure") + repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null) + } else if (authMethod == AuthenticationMethodModel.Sim) { + // Device remains locked while SIM is locked. + Log.d(TAG, "remaining locked because SIM locked") + repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null) + } else { + try { + Log.d(TAG, "started watching for lock and unlock events") + coroutineScope { + launch { + // Unlock the device when a new unlock source is detected. + deviceUnlockSource.collect { + Log.d(TAG, "unlocking due to \"$it\"") + repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, it) } } + + launch { + // Lock events. + merge( + // Device goes to sleep. + powerInteractor.isAsleep + .distinctUntilChanged() + .filter { it } + .map { "asleep" }, + // Device enters lockdown. + isInLockdown + .distinctUntilChanged() + .filter { it } + .map { "lockdown" }, + ) + .collect { reason: String -> + Log.d(TAG, "locking due to \"$reason\"") + repository.deviceUnlockStatus.value = + DeviceUnlockStatus(false, null) + } + } + } + } finally { + Log.d(TAG, "stopped watching for lock and unlock events") } } - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = DeviceUnlockStatus(false, null), - ) + } + + awaitCancellation() + } private fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean { return when (this) { @@ -226,7 +242,20 @@ constructor( return systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE } + /** [CoreStartable] that activates the [DeviceUnlockedInteractor]. */ + class Activator + @Inject + constructor( + @Application private val applicationScope: CoroutineScope, + private val interactor: DeviceUnlockedInteractor, + ) : CoreStartable { + override fun start() { + applicationScope.launch { interactor.activate() } + } + } + companion object { + private val TAG = "DeviceUnlockedInteractor" @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last" @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update" } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt index 42d4effbac3a..63510b873951 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.res.Resources import android.view.LayoutInflater import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY +import com.android.systemui.CoreStartable import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.ConfigurationStateImpl import com.android.systemui.common.ui.GlobalConfig @@ -29,12 +30,16 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl import com.android.systemui.dagger.SysUISingleton import com.android.systemui.res.R +import com.android.systemui.shade.data.repository.ShadePositionRepository +import com.android.systemui.shade.data.repository.ShadePositionRepositoryImpl import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround import com.android.systemui.statusbar.phone.ConfigurationControllerImpl import com.android.systemui.statusbar.phone.ConfigurationForwarder import com.android.systemui.statusbar.policy.ConfigurationController import dagger.Module import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap /** * Module responsible for managing display-specific components and resources for the notification @@ -149,4 +154,25 @@ object ShadeDisplayAwareModule { configurationInteractor } } + + @SysUISingleton + @Provides + @ShadeDisplayAware + fun provideShadePositionRepository(impl: ShadePositionRepositoryImpl): ShadePositionRepository { + ShadeWindowGoesAround.isUnexpectedlyInLegacyMode() + return impl + } + + @Provides + @IntoMap + @ClassKey(ShadePositionRepositoryImpl::class) + fun provideShadePositionRepositoryAsCoreStartable( + impl: ShadePositionRepositoryImpl + ): CoreStartable { + return if (ShadeWindowGoesAround.isEnabled) { + impl + } else { + CoreStartable.NOP + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt new file mode 100644 index 000000000000..802fc0edb533 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt @@ -0,0 +1,56 @@ +/* + * 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.shade + +import android.view.Display +import com.android.systemui.shade.data.repository.ShadePositionRepository +import com.android.systemui.statusbar.commandline.Command +import java.io.PrintWriter + +class ShadePrimaryDisplayCommand(private val positionRepository: ShadePositionRepository) : + Command { + + override fun execute(pw: PrintWriter, args: List<String>) { + if (args[0].lowercase() == "reset") { + positionRepository.resetDisplayId() + pw.println("Reset shade primary display id to ${Display.DEFAULT_DISPLAY}") + return + } + + val displayId: Int = + try { + args[0].toInt() + } catch (e: NumberFormatException) { + pw.println("Error: task id should be an integer") + return + } + + if (displayId < 0) { + pw.println("Error: display id should be positive integer") + } + + positionRepository.setDisplayId(displayId) + pw.println("New shade primary display id is $displayId") + } + + override fun help(pw: PrintWriter) { + pw.println("shade_display_override <displayId> ") + pw.println("Set the display which is holding the shade.") + pw.println("shade_display_override reset ") + pw.println("Reset the display which is holding the shade.") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt new file mode 100644 index 000000000000..24c067ae2371 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade.data.repository + +import android.view.Display +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shade.ShadePrimaryDisplayCommand +import com.android.systemui.statusbar.commandline.CommandRegistry +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +interface ShadePositionRepository { + /** ID of the display which currently hosts the shade */ + val displayId: StateFlow<Int> + + /** + * Updates the value of the shade display id stored, emitting to the new display id to every + * component dependent on the shade display id + */ + fun setDisplayId(displayId: Int) + + /** Resets value of shade primary display to the default display */ + fun resetDisplayId() +} + +/** Source of truth for the display currently holding the shade. */ +@SysUISingleton +class ShadePositionRepositoryImpl +@Inject +constructor(private val commandRegistry: CommandRegistry) : ShadePositionRepository, CoreStartable { + private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY) + + override val displayId: StateFlow<Int> + get() = _displayId + + override fun setDisplayId(displayId: Int) { + _displayId.value = displayId + } + + override fun resetDisplayId() { + _displayId.value = Display.DEFAULT_DISPLAY + } + + override fun start() { + commandRegistry.registerCommand("shade_display_override") { + ShadePrimaryDisplayCommand(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 e9a33e062c60..ad97b21ea60b 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 @@ -348,42 +348,53 @@ constructor( private suspend fun getSettings(): UserSwitcherSettingsModel { return withContext(backgroundDispatcher) { - val isSimpleUserSwitcher = - globalSettings.getInt( - SETTING_SIMPLE_USER_SWITCHER, - if ( - appContext.resources.getBoolean( - com.android.internal.R.bool.config_expandLockScreenUserSwitcher - ) - ) { - 1 - } else { - 0 - }, - ) != 0 - - val isAddUsersFromLockscreen = - globalSettings.getInt(Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0 - - val isUserSwitcherEnabled = - globalSettings.getInt( - Settings.Global.USER_SWITCHER_ENABLED, - if ( - appContext.resources.getBoolean( - com.android.internal.R.bool.config_showUserSwitcherByDefault - ) - ) { - 1 - } else { - 0 - }, - ) != 0 - - UserSwitcherSettingsModel( - isSimpleUserSwitcher = isSimpleUserSwitcher, - isAddUsersFromLockscreen = isAddUsersFromLockscreen, - isUserSwitcherEnabled = isUserSwitcherEnabled, - ) + if ( + // TODO(b/378068979): remove once login screen-specific logic + // is implemented at framework level. + appContext.resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen) + ) { + UserSwitcherSettingsModel( + isSimpleUserSwitcher = false, + isAddUsersFromLockscreen = false, + isUserSwitcherEnabled = false, + ) + } else { + val isSimpleUserSwitcher = + globalSettings.getInt( + SETTING_SIMPLE_USER_SWITCHER, + if ( + appContext.resources.getBoolean( + com.android.internal.R.bool.config_expandLockScreenUserSwitcher + ) + ) { + 1 + } else { + 0 + }, + ) != 0 + + val isAddUsersFromLockscreen = + globalSettings.getInt(Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0 + + val isUserSwitcherEnabled = + globalSettings.getInt( + Settings.Global.USER_SWITCHER_ENABLED, + if ( + appContext.resources.getBoolean( + com.android.internal.R.bool.config_showUserSwitcherByDefault + ) + ) { + 1 + } else { + 0 + }, + ) != 0 + UserSwitcherSettingsModel( + isSimpleUserSwitcher = isSimpleUserSwitcher, + isAddUsersFromLockscreen = isAddUsersFromLockscreen, + isUserSwitcherEnabled = isUserSwitcherEnabled, + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt index a7983605eac9..bcbd679b35eb 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt @@ -21,6 +21,7 @@ import android.graphics.drawable.Drawable import android.os.Handler import android.os.UserManager import android.provider.Settings.Global.USER_SWITCHER_ENABLED +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -40,7 +41,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext interface UserSwitcherRepository { @@ -67,6 +67,9 @@ constructor( private val showUserSwitcherForSingleUser = context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user) + private val userSwitchingMustGoThroughLoginScreen = + context.resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen) + override val isEnabled: Flow<Boolean> = conflatedCallbackFlow { suspend fun updateState() { trySendWithFailureLogging(isUserSwitcherEnabled(), TAG) @@ -135,7 +138,13 @@ constructor( private suspend fun isUserSwitcherEnabled(): Boolean { return withContext(bgDispatcher) { - userManager.isUserSwitcherEnabled(showUserSwitcherForSingleUser) + // TODO(b/378068979): remove once login screen-specific logic + // is implemented at framework level. + if (userSwitchingMustGoThroughLoginScreen) { + false + } else { + userManager.isUserSwitcherEnabled(showUserSwitcherForSingleUser) + } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt index 2dcd275f0103..f6ff4c4a057e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.deviceentry.data.repository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus import dagger.Binds import dagger.Module import javax.inject.Inject @@ -35,6 +36,9 @@ class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository { private var pendingLockscreenEnabled = _isLockscreenEnabled.value + override val deviceUnlockStatus = + MutableStateFlow(DeviceUnlockStatus(isUnlocked = false, deviceUnlockSource = null)) + override suspend fun isLockscreenEnabled(): Boolean { _isLockscreenEnabled.value = pendingLockscreenEnabled return isLockscreenEnabled.value diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt index 8922b2f5c5ef..be84e3316f5b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt @@ -19,25 +19,23 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.deviceentry.data.repository.deviceEntryRepository import com.android.systemui.flags.fakeSystemPropertiesHelper -import com.android.systemui.flags.systemPropertiesHelper -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.trustInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn import com.android.systemui.power.domain.interactor.powerInteractor val Kosmos.deviceUnlockedInteractor by Fixture { DeviceUnlockedInteractor( - applicationScope = applicationCoroutineScope, - authenticationInteractor = authenticationInteractor, - deviceEntryRepository = deviceEntryRepository, - trustInteractor = trustInteractor, - faceAuthInteractor = deviceEntryFaceAuthInteractor, - fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, - powerInteractor = powerInteractor, - biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor, - systemPropertiesHelper = fakeSystemPropertiesHelper, - keyguardTransitionInteractor = keyguardTransitionInteractor, - ) + authenticationInteractor = authenticationInteractor, + repository = deviceEntryRepository, + trustInteractor = trustInteractor, + faceAuthInteractor = deviceEntryFaceAuthInteractor, + fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, + powerInteractor = powerInteractor, + biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor, + systemPropertiesHelper = fakeSystemPropertiesHelper, + ) + .apply { activateIn(testScope) } } 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/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 8dc7c7345f79..6627fcc21cc4 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -144,7 +144,6 @@ public class SettingsToPropertiesMapper { "android_core_networking", "android_health_services", "android_sdk", - "android_stylus", "aoc", "app_widgets", "arc_next", @@ -209,6 +208,7 @@ public class SettingsToPropertiesMapper { "pixel_continuity", "pixel_perf", "pixel_sensors", + "pixel_state_server", "pixel_system_sw_video", "pixel_video_sw", "pixel_watch", diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java index 155ffe805519..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 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/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java index 5febd5c07e3a..5914dbe44b0b 100644 --- a/services/core/java/com/android/server/notification/GroupHelper.java +++ b/services/core/java/com/android/server/notification/GroupHelper.java @@ -788,6 +788,20 @@ public class GroupHelper { return; } + // Check if summary & child notifications are not part of the same section/bundle + // Needs a check here if notification was bundled while enqueued + if (notificationRegroupOnClassification() + && android.service.notification.Flags.notificationClassification()) { + if (isGroupChildBundled(record, summaryByGroupKey)) { + if (DEBUG) { + Slog.v(TAG, "isGroupChildInDifferentBundleThanSummary: " + record); + } + moveNotificationsToNewSection(record.getUserId(), pkgName, + List.of(new NotificationMoveOp(record, null, fullAggregateGroupKey))); + return; + } + } + // scenario 3: sparse/singleton groups if (Flags.notificationForceGroupSingletons()) { try { @@ -800,6 +814,27 @@ public class GroupHelper { } } + private static boolean isGroupChildBundled(final NotificationRecord record, + final Map<String, NotificationRecord> summaryByGroupKey) { + final StatusBarNotification sbn = record.getSbn(); + final String groupKey = record.getSbn().getGroupKey(); + + if (!sbn.isAppGroup()) { + return false; + } + + if (record.getNotification().isGroupSummary()) { + return false; + } + + final NotificationRecord summary = summaryByGroupKey.get(groupKey); + if (summary == null) { + return false; + } + + return NotificationChannel.SYSTEM_RESERVED_IDS.contains(record.getChannel().getId()); + } + /** * Called when a notification is removed, so that this helper can adjust the aggregate groups: * - Removes the autogroup summary of the notification's section @@ -1598,7 +1633,7 @@ public class GroupHelper { final int mSummaryId; private final Predicate<NotificationRecord> mSectionChecker; - public NotificationSectioner(String name, int summaryId, + private NotificationSectioner(String name, int summaryId, Predicate<NotificationRecord> sectionChecker) { mName = name; mSummaryId = summaryId; diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 93482e769a2b..b0ef80793cd7 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -75,9 +75,7 @@ import com.android.internal.util.XmlUtils; import com.android.internal.util.function.TriPredicate; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; -import com.android.server.LocalServices; import com.android.server.notification.NotificationManagerService.DumpFilter; -import com.android.server.pm.UserManagerInternal; import com.android.server.utils.TimingsTraceAndSlog; import org.xmlpull.v1.XmlPullParser; @@ -136,7 +134,6 @@ abstract public class ManagedServices { private final UserProfiles mUserProfiles; protected final IPackageManager mPm; protected final UserManager mUm; - private final UserManagerInternal mUserManagerInternal; private final Config mConfig; private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -198,7 +195,6 @@ abstract public class ManagedServices { mConfig = getConfig(); mApprovalLevel = APPROVAL_BY_COMPONENT; mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); } abstract protected Config getConfig(); @@ -1389,14 +1385,9 @@ abstract public class ManagedServices { @GuardedBy("mMutex") protected void populateComponentsToBind(SparseArray<Set<ComponentName>> componentsToBind, final IntArray activeUsers, - SparseArray<ArraySet<ComponentName>> approvedComponentsByUser, - boolean isVisibleBackgroundUser) { - // When it is a visible background user in Automotive MUMD environment, - // don't clear mEnabledServicesForCurrentProfile and mEnabledServicesPackageNames. - if (!isVisibleBackgroundUser) { - mEnabledServicesForCurrentProfiles.clear(); - mEnabledServicesPackageNames.clear(); - } + SparseArray<ArraySet<ComponentName>> approvedComponentsByUser) { + mEnabledServicesForCurrentProfiles.clear(); + mEnabledServicesPackageNames.clear(); final int nUserIds = activeUsers.size(); for (int i = 0; i < nUserIds; ++i) { @@ -1417,12 +1408,7 @@ abstract public class ManagedServices { } componentsToBind.put(userId, add); - // When it is a visible background user in Automotive MUMD environment, - // skip adding items to mEnabledServicesForCurrentProfile - // and mEnabledServicesPackageNames. - if (isVisibleBackgroundUser) { - continue; - } + mEnabledServicesForCurrentProfiles.addAll(userComponents); for (int j = 0; j < userComponents.size(); j++) { @@ -1470,10 +1456,7 @@ abstract public class ManagedServices { IntArray userIds = mUserProfiles.getCurrentProfileIds(); boolean rebindAllCurrentUsers = mUserProfiles.isProfileUser(userToRebind, mContext) && allowRebindForParentUser(); - boolean isVisibleBackgroundUser = false; if (userToRebind != USER_ALL && !rebindAllCurrentUsers) { - isVisibleBackgroundUser = - mUserManagerInternal.isVisibleBackgroundFullUser(userToRebind); userIds = new IntArray(1); userIds.add(userToRebind); } @@ -1488,8 +1471,7 @@ abstract public class ManagedServices { // Filter approvedComponentsByUser to collect all of the components that are allowed // for the currently active user(s). - populateComponentsToBind(componentsToBind, userIds, approvedComponentsByUser, - isVisibleBackgroundUser); + populateComponentsToBind(componentsToBind, userIds, approvedComponentsByUser); // For every current non-system connection, disconnect services that are no longer // approved, or ALL services if we are force rebinding 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/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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 221b8481a30c..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; @@ -250,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; @@ -2651,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(); } 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/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/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java index c9d5241c57b7..b3ec2153542a 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java +++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java @@ -30,7 +30,6 @@ import android.testing.TestableContext; import androidx.test.InstrumentationRegistry; -import com.android.server.pm.UserManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import org.junit.After; @@ -42,7 +41,6 @@ import org.mockito.MockitoAnnotations; public class UiServiceTestCase { @Mock protected PackageManagerInternal mPmi; - @Mock protected UserManagerInternal mUmi; @Mock protected UriGrantsManagerInternal mUgmInternal; protected static final String PKG_N_MR1 = "com.example.n_mr1"; @@ -94,8 +92,6 @@ public class UiServiceTestCase { } }); - LocalServices.removeServiceForTest(UserManagerInternal.class); - LocalServices.addService(UserManagerInternal.class, mUmi); LocalServices.removeServiceForTest(UriGrantsManagerInternal.class); LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal); when(mUgmInternal.checkGrantUriPermission( 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 38ff3a22022d..cc0286508cdc 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java @@ -2410,11 +2410,13 @@ public class GroupHelperTest extends UiServiceTestCase { NotificationChannel.NEWS_ID); for (NotificationRecord record: notificationList) { if (record.getChannel().getId().equals(channel1.getId()) + && record.getNotification().isGroupChild() && record.getSbn().getId() % 2 == 0) { record.updateNotificationChannel(socialChannel); mGroupHelper.onChannelUpdated(record); } if (record.getChannel().getId().equals(channel1.getId()) + && record.getNotification().isGroupChild() && record.getSbn().getId() % 2 != 0) { record.updateNotificationChannel(newsChannel); mGroupHelper.onChannelUpdated(record); @@ -2474,7 +2476,8 @@ public class GroupHelperTest extends UiServiceTestCase { NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID, IMPORTANCE_DEFAULT); for (NotificationRecord record: notificationList) { - if (record.getChannel().getId().equals(channel1.getId())) { + if (record.getChannel().getId().equals(channel1.getId()) + && record.getNotification().isGroupChild()) { record.updateNotificationChannel(socialChannel); mGroupHelper.onChannelUpdated(record); } @@ -2532,7 +2535,8 @@ public class GroupHelperTest extends UiServiceTestCase { BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, NotificationChannel.SOCIAL_MEDIA_ID); for (NotificationRecord record: notificationList) { - if (record.getOriginalGroupKey().contains("testGrp")) { + if (record.getOriginalGroupKey().contains("testGrp") + && record.getNotification().isGroupChild()) { record.updateNotificationChannel(socialChannel); mGroupHelper.onChannelUpdated(record); } @@ -2631,7 +2635,8 @@ public class GroupHelperTest extends UiServiceTestCase { BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, NotificationChannel.SOCIAL_MEDIA_ID); for (NotificationRecord record: notificationList) { - if (record.getOriginalGroupKey().contains("testGrp")) { + if (record.getOriginalGroupKey().contains("testGrp") + && record.getNotification().isGroupChild()) { record.updateNotificationChannel(socialChannel); mGroupHelper.onChannelUpdated(record); } @@ -2650,6 +2655,64 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION, + FLAG_NOTIFICATION_CLASSIFICATION}) + public void testValidGroupsRegrouped_notificationBundledWhileEnqueued() { + // Check that valid group notifications are regrouped if classification is done + // before onNotificationPostedWithDelay (within DELAY_FOR_ASSISTANT_TIME) + final List<NotificationRecord> notificationList = new ArrayList<>(); + final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>(); + final String pkg = "package"; + + final int summaryId = 0; + final int numChildren = 3; + // Post a regular/valid group: summary + notifications + NotificationRecord summary = getNotificationRecord(pkg, summaryId, + String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true); + notificationList.add(summary); + summaryByGroup.put(summary.getGroupKey(), summary); + for (int i = 0; i < numChildren; i++) { + NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42), + UserHandle.SYSTEM, "testGrp", false); + notificationList.add(child); + } + + // Classify/bundle child notifications. Don't call onChannelUpdated, + // adjustments applied while enqueued will use NotificationAdjustmentExtractor. + final NotificationChannel socialChannel = new NotificationChannel( + NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID, + IMPORTANCE_DEFAULT); + final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg, + AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier()); + final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes( + BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT, + NotificationChannel.SOCIAL_MEDIA_ID); + for (NotificationRecord record: notificationList) { + if (record.getOriginalGroupKey().contains("testGrp") + && record.getNotification().isGroupChild()) { + record.updateNotificationChannel(socialChannel); + } + } + + // Check that notifications are forced grouped and app-provided summaries are canceled + for (NotificationRecord record: notificationList) { + mGroupHelper.onNotificationPostedWithDelay(record, notificationList, summaryByGroup); + } + + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social)); + verify(mCallback, times(numChildren)).addAutoGroup(anyString(), eq(expectedGroupKey_social), + eq(true)); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString()); + verify(mCallback, times(numChildren - 1)).updateAutogroupSummary(anyInt(), anyString(), + anyString(), any()); + verify(mCallback, times(numChildren)).removeAppProvidedSummaryOnClassification(anyString(), + anyString()); + } + + @Test @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING) public void testMoveAggregateGroups_updateChannel_groupsUngrouped() { final String pkg = "package"; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 48bc9d7c51a1..e5c42082ab97 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -1484,7 +1484,6 @@ public class ManagedServicesTest extends UiServiceTestCase { assertTrue(componentsToUnbind.get(0).contains(ComponentName.unflattenFromString("c/c"))); } - @SuppressWarnings("GuardedBy") @Test public void populateComponentsToBind() { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, @@ -1508,8 +1507,7 @@ public class ManagedServicesTest extends UiServiceTestCase { SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>(); - service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser, - /* isVisibleBackgroundUser= */ false); + service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser); assertEquals(2, componentsToBind.size()); assertEquals(1, componentsToBind.get(0).size()); @@ -1519,33 +1517,6 @@ public class ManagedServicesTest extends UiServiceTestCase { assertTrue(componentsToBind.get(10).contains(ComponentName.unflattenFromString("c/c"))); } - @SuppressWarnings("GuardedBy") - @Test - public void populateComponentsToBind_isVisibleBackgroundUser_addComponentsToBindButNotAddToEnabledComponent() { - ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm, - APPROVAL_BY_COMPONENT); - - SparseArray<ArraySet<ComponentName>> approvedComponentsByUser = new SparseArray<>(); - ArraySet<ComponentName> allowed = new ArraySet<>(); - allowed.add(ComponentName.unflattenFromString("pkg1/cmp1")); - approvedComponentsByUser.put(11, allowed); - IntArray users = new IntArray(); - users.add(11); - - SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>(); - - service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser, - /* isVisibleBackgroundUser= */ true); - - assertEquals(1, componentsToBind.size()); - assertEquals(1, componentsToBind.get(11).size()); - assertTrue(componentsToBind.get(11).contains(ComponentName.unflattenFromString( - "pkg1/cmp1"))); - assertThat(service.isComponentEnabledForCurrentProfiles( - new ComponentName("pkg1", "cmp1"))).isFalse(); - assertThat(service.isComponentEnabledForPackage("pkg1")).isFalse(); - } - @Test public void testOnNullBinding() throws Exception { Context context = mock(Context.class); 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/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt index 332b9b832037..9a9a331a3753 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt @@ -21,7 +21,7 @@ import android.graphics.Insets import android.graphics.Rect import android.graphics.Region import android.os.SystemClock -import android.platform.uiautomator_helpers.DeviceHelpers +import android.platform.uiautomatorhelpers.DeviceHelpers import android.tools.device.apphelpers.IStandardAppHelper import android.tools.helpers.SYSTEMUI_PACKAGE import android.tools.traces.parsers.WindowManagerStateHelper @@ -159,7 +159,7 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : ) { val caption = getCaptionForTheApp(wmHelper, device) val maximizeButton = getMaximizeButtonForTheApp(caption) - maximizeButton?.longClick() + maximizeButton.longClick() wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() val buttonResId = if (toLeft) SNAP_LEFT_BUTTON else SNAP_RIGHT_BUTTON 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 } |