From 193643cbedd3b52a86c9ee5857009e2a75edc9d7 Mon Sep 17 00:00:00 2001 From: Utkarsh Nigam Date: Wed, 25 Sep 2024 10:59:32 +0000 Subject: Update Sidecar Library with CancellationSignal. Change-Id: I9df773bc51228eb52578a05e219f183863cae753 Flag: android.app.appfunctions.flags.enable_app_function_manager Test: atest CtsAppFunctionTestCases Bug: 360864791 --- .../appfunctions/sidecar/AppFunctionManager.java | 42 +++++++++++++++++++--- .../appfunctions/sidecar/AppFunctionService.java | 37 ++++++++++++++++++- 2 files changed, 73 insertions(+), 6 deletions(-) (limited to 'libs/appfunctions/java') diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java index b1dd4676a35e..815fe05cc3ab 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java @@ -19,12 +19,12 @@ package com.google.android.appfunctions.sidecar; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.content.Context; +import android.os.CancellationSignal; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; - /** * Provides app functions related functionalities. * @@ -45,7 +45,7 @@ public final class AppFunctionManager { * * @param context A {@link Context}. * @throws java.lang.IllegalStateException if the underlying {@link - * android.app.appfunctions.AppFunctionManager} is not found. + * android.app.appfunctions.AppFunctionManager} is not found. */ public AppFunctionManager(Context context) { mContext = Objects.requireNonNull(context); @@ -66,6 +66,7 @@ public final class AppFunctionManager { public void executeAppFunction( @NonNull ExecuteAppFunctionRequest sidecarRequest, @NonNull @CallbackExecutor Executor executor, + @NonNull CancellationSignal cancellationSignal, @NonNull Consumer callback) { Objects.requireNonNull(sidecarRequest); Objects.requireNonNull(executor); @@ -74,9 +75,40 @@ public final class AppFunctionManager { android.app.appfunctions.ExecuteAppFunctionRequest platformRequest = SidecarConverter.getPlatformExecuteAppFunctionRequest(sidecarRequest); mManager.executeAppFunction( - platformRequest, executor, (platformResponse) -> { - callback.accept(SidecarConverter.getSidecarExecuteAppFunctionResponse( - platformResponse)); + platformRequest, + executor, + cancellationSignal, + (platformResponse) -> { + callback.accept( + SidecarConverter.getSidecarExecuteAppFunctionResponse( + platformResponse)); }); } + + /** + * Executes the app function. + * + *

Proxies request and response to the underlying {@link + * android.app.appfunctions.AppFunctionManager#executeAppFunction}, converting the request and + * response in the appropriate type required by the function. + * + * @deprecated Use {@link #executeAppFunction(ExecuteAppFunctionRequest, Executor, + * CancellationSignal, Consumer)} instead. This method will be removed once usage references + * are updated. + */ + @Deprecated + public void executeAppFunction( + @NonNull ExecuteAppFunctionRequest sidecarRequest, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer callback) { + Objects.requireNonNull(sidecarRequest); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + executeAppFunction( + sidecarRequest, + executor, + new CancellationSignal(), + callback); + } } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java index 65959dfdf561..6023c977bd76 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java @@ -25,6 +25,7 @@ import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; +import android.os.CancellationSignal; import java.util.function.Consumer; @@ -69,10 +70,11 @@ public abstract class AppFunctionService extends Service { private final Binder mBinder = android.app.appfunctions.AppFunctionService.createBinder( /* context= */ this, - /* onExecuteFunction= */ (platformRequest, callback) -> { + /* onExecuteFunction= */ (platformRequest, cancellationSignal, callback) -> { AppFunctionService.this.onExecuteFunction( SidecarConverter.getSidecarExecuteAppFunctionRequest( platformRequest), + cancellationSignal, (sidecarResponse) -> { callback.accept( SidecarConverter.getPlatformExecuteAppFunctionResponse( @@ -105,9 +107,42 @@ public abstract class AppFunctionService extends Service { * result using the callback, no matter if the execution was successful or not. * * @param request The function execution request. + * @param cancellationSignal A {@link CancellationSignal} to cancel the request. * @param callback A callback to report back the result. */ @MainThread + public void onExecuteFunction( + @NonNull ExecuteAppFunctionRequest request, + @NonNull CancellationSignal cancellationSignal, + @NonNull Consumer callback) { + onExecuteFunction(request, callback); + } + + /** + * Called by the system to execute a specific app function. + * + *

This method is triggered when the system requests your AppFunctionService to handle a + * particular function you have registered and made available. + * + *

To ensure proper routing of function requests, assign a unique identifier to each + * function. This identifier doesn't need to be globally unique, but it must be unique within + * your app. For example, a function to order food could be identified as "orderFood". In most + * cases this identifier should come from the ID automatically generated by the AppFunctions + * SDK. You can determine the specific function to invoke by calling {@link + * ExecuteAppFunctionRequest#getFunctionIdentifier()}. + * + *

This method is always triggered in the main thread. You should run heavy tasks on a worker + * thread and dispatch the result with the given callback. You should always report back the + * result using the callback, no matter if the execution was successful or not. + * + * @param request The function execution request. + * @param callback A callback to report back the result. + * + * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal, + * Consumer)} instead. This method will be removed once usage references are updated. + */ + @MainThread + @Deprecated public abstract void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull Consumer callback); -- cgit v1.2.3-59-g8ed1b From 2a17dcdc1d48bdf46ddbfedc6ccc038be6ac12f7 Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Fri, 27 Sep 2024 17:22:36 +0100 Subject: Add is/setAppFunctionEnabled to sidecar Flag: android.app.appfunctions.flags.enable_app_function_manager Test: atest CtsAppFunctionTestCases -c BUG: 357551503 Change-Id: I4b9fd044da76f1ced1c7be0c6d894fc4e70d2a90 --- libs/appfunctions/api/current.txt | 6 ++ .../appfunctions/sidecar/AppFunctionManager.java | 100 +++++++++++++++++++++ .../sidecar/ExecuteAppFunctionResponse.java | 4 + 3 files changed, 110 insertions(+) (limited to 'libs/appfunctions/java') diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index 3ed33db2222e..bb0fc41acd30 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -5,6 +5,11 @@ package com.google.android.appfunctions.sidecar { ctor public AppFunctionManager(android.content.Context); method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); method @Deprecated public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); + method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); + field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0 + field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2 + field public static final int APP_FUNCTION_STATE_ENABLED = 1; // 0x1 } public abstract class AppFunctionService extends android.app.Service { @@ -41,6 +46,7 @@ package com.google.android.appfunctions.sidecar { field public static final String PROPERTY_RETURN_VALUE = "returnValue"; field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2 field public static final int RESULT_DENIED = 1; // 0x1 + field public static final int RESULT_DISABLED = 6; // 0x6 field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3 field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4 field public static final int RESULT_OK = 0; // 0x0 diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java index 815fe05cc3ab..d660926575d1 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java @@ -16,11 +16,18 @@ package com.google.android.appfunctions.sidecar; +import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.annotation.UserHandleAware; import android.content.Context; import android.os.CancellationSignal; +import android.os.OutcomeReceiver; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -37,6 +44,39 @@ import java.util.function.Consumer; // TODO(b/357551503): Implement get and set enabled app function APIs. // TODO(b/367329899): Add sidecar library to Android B builds. public final class AppFunctionManager { + /** + * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset + * enabled state to the default value. + */ + public static final int APP_FUNCTION_STATE_DEFAULT = 0; + + /** + * The app function is enabled. To enable an app function, call {@link #setAppFunctionEnabled} + * with this value. + */ + public static final int APP_FUNCTION_STATE_ENABLED = 1; + + /** + * The app function is disabled. To disable an app function, call {@link #setAppFunctionEnabled} + * with this value. + */ + public static final int APP_FUNCTION_STATE_DISABLED = 2; + + /** + * The enabled state of the app function. + * + * @hide + */ + @IntDef( + prefix = {"APP_FUNCTION_STATE_"}, + value = { + APP_FUNCTION_STATE_DEFAULT, + APP_FUNCTION_STATE_ENABLED, + APP_FUNCTION_STATE_DISABLED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EnabledState {} + private final android.app.appfunctions.AppFunctionManager mManager; private final Context mContext; @@ -111,4 +151,64 @@ public final class AppFunctionManager { new CancellationSignal(), callback); } + + /** + * Returns a boolean through a callback, indicating whether the app function is enabled. + * + *

* This method can only check app functions owned by the caller, or those where the caller + * has visibility to the owner package and holds either the {@link + * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link + * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission. + * + *

If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: + * + *

+ * + * @param functionIdentifier the identifier of the app function to check (unique within the + * target package) and in most cases, these are automatically generated by the AppFunctions + * SDK + * @param targetPackage the package name of the app function's owner + * @param executor the executor to run the request + * @param callback the callback to receive the function enabled check result + */ + public void isAppFunctionEnabled( + @NonNull String functionIdentifier, + @NonNull String targetPackage, + @NonNull Executor executor, + @NonNull OutcomeReceiver callback) { + mManager.isAppFunctionEnabled(functionIdentifier, targetPackage, executor, callback); + } + + /** + * Sets the enabled state of the app function owned by the calling package. + * + *

If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: + * + *

    + *
  • {@link IllegalArgumentException}, if the function is not found or the caller does not + * have access to it. + *
+ * + * @param functionIdentifier the identifier of the app function to enable (unique within the + * calling package). In most cases, identifiers are automatically generated by the + * AppFunctions SDK + * @param newEnabledState the new state of the app function + * @param executor the executor to run the callback + * @param callback the callback to receive the result of the function enablement. The call was + * successful if no exception was thrown. + */ + // Constants in @EnabledState should always mirror those in + // android.app.appfunctions.AppFunctionManager. + @SuppressLint("WrongConstant") + @UserHandleAware + public void setAppFunctionEnabled( + @NonNull String functionIdentifier, + @EnabledState int newEnabledState, + @NonNull Executor executor, + @NonNull OutcomeReceiver callback) { + mManager.setAppFunctionEnabled(functionIdentifier, newEnabledState, executor, callback); + } } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java index 60c25fae58d1..c7ce95bab7a5 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java @@ -76,6 +76,9 @@ public final class ExecuteAppFunctionResponse { /** The operation was timed out. */ public static final int RESULT_TIMED_OUT = 5; + /** The caller tried to execute a disabled app function. */ + public static final int RESULT_DISABLED = 6; + /** The result code of the app function execution. */ @ResultCode private final int mResultCode; @@ -234,6 +237,7 @@ public final class ExecuteAppFunctionResponse { RESULT_INTERNAL_ERROR, RESULT_INVALID_ARGUMENT, RESULT_TIMED_OUT, + RESULT_DISABLED }) @Retention(RetentionPolicy.SOURCE) public @interface ResultCode {} -- cgit v1.2.3-59-g8ed1b From ae78431560d32230dc2bf3eb9774c6025f86f42c Mon Sep 17 00:00:00 2001 From: Utkarsh Nigam Date: Thu, 3 Oct 2024 14:07:54 +0000 Subject: Make onExecuteAppFunction non-abstract and remove timeout results. This is required so g3 usages can stop overriding this deprecated method and the timeout logic was removed in ag/29477656 Change-Id: Icd07685ea296403b48f4c3cbd74c2e7c3ad28430 Flag: android.app.appfunctions.flags.enable_app_function_manager Test: atest CtsAppFunctionTestCases -c Bug: 360864791 --- core/api/current.txt | 7 +++---- core/java/android/app/appfunctions/AppFunctionService.java | 9 +++++++-- .../android/app/appfunctions/ExecuteAppFunctionResponse.java | 8 ++------ libs/appfunctions/api/current.txt | 6 +++--- .../android/appfunctions/sidecar/AppFunctionService.java | 9 +++++++-- .../appfunctions/sidecar/ExecuteAppFunctionResponse.java | 12 +++++++----- .../server/appfunctions/AppFunctionManagerServiceImpl.java | 2 +- 7 files changed, 30 insertions(+), 23 deletions(-) (limited to 'libs/appfunctions/java') diff --git a/core/api/current.txt b/core/api/current.txt index 67b328057efe..bc3ffe86edfc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8787,7 +8787,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 @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer); + method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer); method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } @@ -8822,13 +8822,12 @@ package android.app.appfunctions { field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final String PROPERTY_RETURN_VALUE = "returnValue"; field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2 - field public static final int RESULT_CANCELLED = 7; // 0x7 + field public static final int RESULT_CANCELLED = 6; // 0x6 field public static final int RESULT_DENIED = 1; // 0x1 - field public static final int RESULT_DISABLED = 6; // 0x6 + field public static final int RESULT_DISABLED = 5; // 0x5 field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3 field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4 field public static final int RESULT_OK = 0; // 0x0 - field public static final int RESULT_TIMED_OUT = 5; // 0x5 } } diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java index 8e417737515e..7a68a656564b 100644 --- a/core/java/android/app/appfunctions/AppFunctionService.java +++ b/core/java/android/app/appfunctions/AppFunctionService.java @@ -35,6 +35,7 @@ import android.os.ICancellationSignal; import android.os.CancellationSignal; import android.os.RemoteCallback; import android.os.RemoteException; +import android.util.Log; import java.util.function.Consumer; @@ -166,9 +167,13 @@ public abstract class AppFunctionService extends Service { */ @MainThread @Deprecated - public abstract void onExecuteFunction( + public void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, - @NonNull Consumer callback); + @NonNull Consumer callback) { + Log.w( + "AppFunctionService", + "Calling deprecated default implementation of onExecuteFunction"); + } /** * Called by the system to execute a specific app function. diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java index 2851e92bd57f..a879b1ba6b5c 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java @@ -96,17 +96,14 @@ public final class ExecuteAppFunctionResponse implements Parcelable { */ public static final int RESULT_INVALID_ARGUMENT = 4; - /** The operation was timed out. */ - public static final int RESULT_TIMED_OUT = 5; - /** The caller tried to execute a disabled app function. */ - public static final int RESULT_DISABLED = 6; + public static final int RESULT_DISABLED = 5; /** * The operation was cancelled. Use this error code to report that a cancellation is done after * receiving a cancellation signal. */ - public static final int RESULT_CANCELLED = 7; + public static final int RESULT_CANCELLED = 6; /** The result code of the app function execution. */ @ResultCode private final int mResultCode; @@ -282,7 +279,6 @@ public final class ExecuteAppFunctionResponse implements Parcelable { RESULT_APP_UNKNOWN_ERROR, RESULT_INTERNAL_ERROR, RESULT_INVALID_ARGUMENT, - RESULT_TIMED_OUT, RESULT_DISABLED, RESULT_CANCELLED }) diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index bb0fc41acd30..bc269fedddfe 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -16,7 +16,7 @@ package com.google.android.appfunctions.sidecar { ctor public AppFunctionService(); method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); - method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer); + method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer); 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"; } @@ -45,12 +45,12 @@ package com.google.android.appfunctions.sidecar { method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle); field public static final String PROPERTY_RETURN_VALUE = "returnValue"; field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2 + field public static final int RESULT_CANCELLED = 6; // 0x6 field public static final int RESULT_DENIED = 1; // 0x1 - field public static final int RESULT_DISABLED = 6; // 0x6 + field public static final int RESULT_DISABLED = 5; // 0x5 field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3 field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4 field public static final int RESULT_OK = 0; // 0x0 - field public static final int RESULT_TIMED_OUT = 5; // 0x5 } } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java index 6023c977bd76..6e91de6bbcf2 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java @@ -26,6 +26,7 @@ import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.os.CancellationSignal; +import android.util.Log; import java.util.function.Consumer; @@ -143,7 +144,11 @@ public abstract class AppFunctionService extends Service { */ @MainThread @Deprecated - public abstract void onExecuteFunction( + public void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, - @NonNull Consumer callback); + @NonNull Consumer callback) { + Log.w( + "AppFunctionService", + "Calling deprecated default implementation of onExecuteFunction"); + } } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java index c7ce95bab7a5..d87fec7985e9 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java @@ -73,11 +73,14 @@ public final class ExecuteAppFunctionResponse { */ public static final int RESULT_INVALID_ARGUMENT = 4; - /** The operation was timed out. */ - public static final int RESULT_TIMED_OUT = 5; - /** The caller tried to execute a disabled app function. */ - public static final int RESULT_DISABLED = 6; + public static final int RESULT_DISABLED = 5; + + /** + * The operation was cancelled. Use this error code to report that a cancellation is done after + * receiving a cancellation signal. + */ + public static final int RESULT_CANCELLED = 6; /** The result code of the app function execution. */ @ResultCode private final int mResultCode; @@ -236,7 +239,6 @@ public final class ExecuteAppFunctionResponse { RESULT_APP_UNKNOWN_ERROR, RESULT_INTERNAL_ERROR, RESULT_INVALID_ARGUMENT, - RESULT_TIMED_OUT, RESULT_DISABLED }) @Retention(RetentionPolicy.SOURCE) diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index c87d516d2ab4..2d9e76b73291 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -492,7 +492,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { Slog.e(TAG, "Failed to bind to the AppFunctionService"); safeExecuteAppFunctionCallback.onResult( ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_TIMED_OUT, + ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR, "Failed to bind the AppFunctionService.", /* extras= */ null)); } -- cgit v1.2.3-59-g8ed1b From d24ae5991d0dfe0fca7e67c890e8c5a9dd32f942 Mon Sep 17 00:00:00 2001 From: Utkarsh Nigam Date: Fri, 4 Oct 2024 09:56:29 +0000 Subject: Add calling package onExecuteAppFunction Flag: android.app.appfunctions.flags.enable_app_function_manager Test: cts Bug: 357551503 Change-Id: I54440175a5c327d0af4b3605904064c080dd76ff --- core/api/current.txt | 3 +- .../app/appfunctions/AppFunctionService.java | 54 ++++++++++++++++++---- .../app/appfunctions/IAppFunctionService.aidl | 2 + libs/appfunctions/api/current.txt | 3 +- .../appfunctions/sidecar/AppFunctionService.java | 50 ++++++++++++++++++-- .../sidecar/ExecuteAppFunctionResponse.java | 13 +++--- .../AppFunctionManagerServiceImpl.java | 2 +- .../RunAppFunctionServiceCallback.java | 22 ++------- 8 files changed, 108 insertions(+), 41 deletions(-) (limited to 'libs/appfunctions/java') diff --git a/core/api/current.txt b/core/api/current.txt index 4f913613cfd5..8478b409b12a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8788,7 +8788,8 @@ package android.app.appfunctions { ctor public AppFunctionService(); method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer); - method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); + method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); + method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java index 7a68a656564b..ceca850a1037 100644 --- a/core/java/android/app/appfunctions/AppFunctionService.java +++ b/core/java/android/app/appfunctions/AppFunctionService.java @@ -29,11 +29,9 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Binder; -import android.os.Bundle; +import android.os.CancellationSignal; import android.os.IBinder; import android.os.ICancellationSignal; -import android.os.CancellationSignal; -import android.os.RemoteCallback; import android.os.RemoteException; import android.util.Log; @@ -80,6 +78,7 @@ public abstract class AppFunctionService extends Service { */ void perform( @NonNull ExecuteAppFunctionRequest request, + @NonNull String callingPackage, @NonNull CancellationSignal cancellationSignal, @NonNull Consumer callback); } @@ -92,6 +91,7 @@ public abstract class AppFunctionService extends Service { @Override public void executeAppFunction( @NonNull ExecuteAppFunctionRequest request, + @NonNull String callingPackage, @NonNull ICancellationCallback cancellationCallback, @NonNull IExecuteAppFunctionCallback callback) { if (context.checkCallingPermission(BIND_APP_FUNCTION_SERVICE) @@ -103,6 +103,7 @@ public abstract class AppFunctionService extends Service { try { onExecuteFunction.perform( request, + callingPackage, buildCancellationSignal(cancellationCallback), safeCallback::onResult); } catch (Exception ex) { @@ -128,12 +129,11 @@ public abstract class AppFunctionService extends Service { throw e.rethrowFromSystemServer(); } - return cancellationSignal ; + return cancellationSignal; } - private final Binder mBinder = createBinder( - AppFunctionService.this, - AppFunctionService.this::onExecuteFunction); + private final Binder mBinder = + createBinder(AppFunctionService.this, AppFunctionService.this::onExecuteFunction); @NonNull @Override @@ -141,7 +141,6 @@ public abstract class AppFunctionService extends Service { return mBinder; } - /** * Called by the system to execute a specific app function. * @@ -161,7 +160,6 @@ public abstract class AppFunctionService extends Service { * * @param request The function execution request. * @param callback A callback to report back the result. - * * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal, * Consumer)} instead. This method will be removed once usage references are updated. */ @@ -198,12 +196,50 @@ public abstract class AppFunctionService extends Service { * @param request The function execution request. * @param cancellationSignal A signal to cancel the execution. * @param callback A callback to report back the result. + * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, String, + * CancellationSignal, Consumer)} instead. This method will be removed once usage references + * are updated. */ @MainThread + @Deprecated public void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull Consumer callback) { onExecuteFunction(request, callback); } + + /** + * Called by the system to execute a specific app function. + * + *

This method is triggered when the system requests your AppFunctionService to handle a + * particular function you have registered and made available. + * + *

To ensure proper routing of function requests, assign a unique identifier to each + * function. This identifier doesn't need to be globally unique, but it must be unique within + * your app. For example, a function to order food could be identified as "orderFood". In most + * cases this identifier should come from the ID automatically generated by the AppFunctions + * SDK. You can determine the specific function to invoke by calling {@link + * ExecuteAppFunctionRequest#getFunctionIdentifier()}. + * + *

This method is always triggered in the main thread. You should run heavy tasks on a worker + * thread and dispatch the result with the given callback. You should always report back the + * result using the callback, no matter if the execution was successful or not. + * + *

This method also accepts a {@link CancellationSignal} that the app should listen to cancel + * the execution of function if requested by the system. + * + * @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. + */ + @MainThread + public void onExecuteFunction( + @NonNull ExecuteAppFunctionRequest request, + @NonNull String callingPackage, + @NonNull CancellationSignal cancellationSignal, + @NonNull Consumer callback) { + onExecuteFunction(request, cancellationSignal, callback); + } } diff --git a/core/java/android/app/appfunctions/IAppFunctionService.aidl b/core/java/android/app/appfunctions/IAppFunctionService.aidl index 291f33ccb1b8..bf935d2a102b 100644 --- a/core/java/android/app/appfunctions/IAppFunctionService.aidl +++ b/core/java/android/app/appfunctions/IAppFunctionService.aidl @@ -34,11 +34,13 @@ oneway interface IAppFunctionService { * Called by the system to execute a specific app function. * * @param request the function execution request. + * @param callingPackage The package name of the app that is requesting the execution. * @param cancellationCallback a callback to send back the cancellation transport. * @param callback a callback to report back the result. */ void executeAppFunction( in ExecuteAppFunctionRequest request, + in String callingPackage, in ICancellationCallback cancellationCallback, in IExecuteAppFunctionCallback callback ); diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index bc269fedddfe..e9845c1d9f13 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -15,7 +15,8 @@ package com.google.android.appfunctions.sidecar { public abstract class AppFunctionService extends android.app.Service { ctor public AppFunctionService(); method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); + method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); + method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer); 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"; diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java index 6e91de6bbcf2..2a168e871713 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java @@ -24,8 +24,8 @@ import android.annotation.Nullable; import android.app.Service; import android.content.Intent; import android.os.Binder; -import android.os.IBinder; import android.os.CancellationSignal; +import android.os.IBinder; import android.util.Log; import java.util.function.Consumer; @@ -71,18 +71,21 @@ public abstract class AppFunctionService extends Service { private final Binder mBinder = android.app.appfunctions.AppFunctionService.createBinder( /* context= */ this, - /* onExecuteFunction= */ (platformRequest, cancellationSignal, callback) -> { + /* onExecuteFunction= */ (platformRequest, + callingPackage, + cancellationSignal, + callback) -> { AppFunctionService.this.onExecuteFunction( SidecarConverter.getSidecarExecuteAppFunctionRequest( platformRequest), + callingPackage, cancellationSignal, (sidecarResponse) -> { callback.accept( SidecarConverter.getPlatformExecuteAppFunctionResponse( sidecarResponse)); }); - } - ); + }); @NonNull @Override @@ -90,6 +93,40 @@ public abstract class AppFunctionService extends Service { return mBinder; } + /** + * Called by the system to execute a specific app function. + * + *

This method is triggered when the system requests your AppFunctionService to handle a + * particular function you have registered and made available. + * + *

To ensure proper routing of function requests, assign a unique identifier to each + * function. This identifier doesn't need to be globally unique, but it must be unique within + * your app. For example, a function to order food could be identified as "orderFood". In most + * cases this identifier should come from the ID automatically generated by the AppFunctions + * SDK. You can determine the specific function to invoke by calling {@link + * ExecuteAppFunctionRequest#getFunctionIdentifier()}. + * + *

This method is always triggered in the main thread. You should run heavy tasks on a worker + * thread and dispatch the result with the given callback. You should always report back the + * result using the callback, no matter if the execution was successful or not. + * + *

This method also accepts a {@link CancellationSignal} that the app should listen to cancel + * the execution of function if requested by the system. + * + * @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. + */ + @MainThread + public void onExecuteFunction( + @NonNull ExecuteAppFunctionRequest request, + @NonNull String callingPackage, + @NonNull CancellationSignal cancellationSignal, + @NonNull Consumer callback) { + onExecuteFunction(request, cancellationSignal, callback); + } + /** * Called by the system to execute a specific app function. * @@ -110,8 +147,12 @@ public abstract class AppFunctionService extends Service { * @param request The function execution request. * @param cancellationSignal A {@link CancellationSignal} to cancel the request. * @param callback A callback to report back the result. + * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, String, + * CancellationSignal, Consumer)} instead. This method will be removed once usage references + * are updated. */ @MainThread + @Deprecated public void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull CancellationSignal cancellationSignal, @@ -138,7 +179,6 @@ public abstract class AppFunctionService extends Service { * * @param request The function execution request. * @param callback A callback to report back the result. - * * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal, * Consumer)} instead. This method will be removed once usage references are updated. */ diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java index d87fec7985e9..969e5d58b909 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java @@ -234,12 +234,13 @@ public final class ExecuteAppFunctionResponse { @IntDef( prefix = {"RESULT_"}, value = { - RESULT_OK, - RESULT_DENIED, - RESULT_APP_UNKNOWN_ERROR, - RESULT_INTERNAL_ERROR, - RESULT_INVALID_ARGUMENT, - RESULT_DISABLED + RESULT_OK, + RESULT_DENIED, + RESULT_APP_UNKNOWN_ERROR, + RESULT_INTERNAL_ERROR, + RESULT_INVALID_ARGUMENT, + RESULT_DISABLED, + RESULT_CANCELLED }) @Retention(RetentionPolicy.SOURCE) public @interface ResultCode {} diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index d0c3dafd6e81..853399b79342 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -438,7 +438,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { targetUser, mServiceConfig.getExecuteAppFunctionCancellationTimeoutMillis(), cancellationSignal, - RunAppFunctionServiceCallback.create( + new RunAppFunctionServiceCallback( requestInternal, cancellationCallback, safeExecuteAppFunctionCallback), diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java index 7820390dd544..129be65f3153 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java +++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java @@ -27,17 +27,17 @@ import android.util.Slog; import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback; import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener; - /** * A callback to forward a request to the {@link IAppFunctionService} and report back the result. */ public class RunAppFunctionServiceCallback implements RunServiceCallCallback { + private static final String TAG = RunAppFunctionServiceCallback.class.getSimpleName(); private final ExecuteAppFunctionAidlRequest mRequestInternal; private final SafeOneTimeExecuteAppFunctionCallback mSafeExecuteAppFunctionCallback; private final ICancellationCallback mCancellationCallback; - private RunAppFunctionServiceCallback( + public RunAppFunctionServiceCallback( ExecuteAppFunctionAidlRequest requestInternal, ICancellationCallback cancellationCallback, SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) { @@ -46,21 +46,6 @@ public class RunAppFunctionServiceCallback implements RunServiceCallCallback Date: Wed, 16 Oct 2024 17:31:20 +0000 Subject: Remove deprecated methods from AppFunctionManager and AppFunctionService. All usage references have been updated. Change-Id: I9bd64c3bf2fba09d68c55a8a82e075250690cc09 Flag: android.app.appfunctions.flags.enable_app_function_manager Test: CTS Bug: 357551503 --- core/api/current.txt | 5 +- .../app/appfunctions/AppFunctionManager.java | 34 ---------- .../app/appfunctions/AppFunctionService.java | 74 +--------------------- libs/appfunctions/api/current.txt | 5 +- .../appfunctions/sidecar/AppFunctionManager.java | 27 -------- .../appfunctions/sidecar/AppFunctionService.java | 71 +-------------------- 6 files changed, 6 insertions(+), 210 deletions(-) (limited to 'libs/appfunctions/java') diff --git a/core/api/current.txt b/core/api/current.txt index f59b57528ef6..14a91c9033c5 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8781,7 +8781,6 @@ package android.app.admin { package android.app.appfunctions { @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager { - method @Deprecated @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 java.util.function.Consumer); 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); method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); @@ -8793,9 +8792,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 @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer); - method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); - method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); + method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java index 439d988e2588..dca433696fe7 100644 --- a/core/java/android/app/appfunctions/AppFunctionManager.java +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -100,40 +100,6 @@ public final class AppFunctionManager { mContext = context; } - /** - * Executes the app function. - * - *

Note: Applications can execute functions they define. To execute functions defined in - * another component, apps would need to have {@code - * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code - * android.permission.EXECUTE_APP_FUNCTIONS}. - * - * @param request the request to execute the app function - * @param executor the executor to run the callback - * @param callback the callback to receive the function execution result. 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}. - * @deprecated Use {@link #executeAppFunction(ExecuteAppFunctionRequest, Executor, - * CancellationSignal, Consumer)} instead. This method will be removed once usage references - * are updated. - */ - @RequiresPermission( - anyOf = { - Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, - Manifest.permission.EXECUTE_APP_FUNCTIONS - }, - conditional = true) - @UserHandleAware - @Deprecated - public void executeAppFunction( - @NonNull ExecuteAppFunctionRequest request, - @NonNull @CallbackExecutor Executor executor, - @NonNull Consumer callback) { - executeAppFunction(request, executor, new CancellationSignal(), callback); - } - /** * Executes the app function. * diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java index ceca850a1037..63d187aa11ef 100644 --- a/core/java/android/app/appfunctions/AppFunctionService.java +++ b/core/java/android/app/appfunctions/AppFunctionService.java @@ -141,74 +141,6 @@ public abstract class AppFunctionService extends Service { return mBinder; } - /** - * Called by the system to execute a specific app function. - * - *

This method is triggered when the system requests your AppFunctionService to handle a - * particular function you have registered and made available. - * - *

To ensure proper routing of function requests, assign a unique identifier to each - * function. This identifier doesn't need to be globally unique, but it must be unique within - * your app. For example, a function to order food could be identified as "orderFood". In most - * cases this identifier should come from the ID automatically generated by the AppFunctions - * SDK. You can determine the specific function to invoke by calling {@link - * ExecuteAppFunctionRequest#getFunctionIdentifier()}. - * - *

This method is always triggered in the main thread. You should run heavy tasks on a worker - * thread and dispatch the result with the given callback. You should always report back the - * result using the callback, no matter if the execution was successful or not. - * - * @param request The function execution request. - * @param callback A callback to report back the result. - * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal, - * Consumer)} instead. This method will be removed once usage references are updated. - */ - @MainThread - @Deprecated - public void onExecuteFunction( - @NonNull ExecuteAppFunctionRequest request, - @NonNull Consumer callback) { - Log.w( - "AppFunctionService", - "Calling deprecated default implementation of onExecuteFunction"); - } - - /** - * Called by the system to execute a specific app function. - * - *

This method is triggered when the system requests your AppFunctionService to handle a - * particular function you have registered and made available. - * - *

To ensure proper routing of function requests, assign a unique identifier to each - * function. This identifier doesn't need to be globally unique, but it must be unique within - * your app. For example, a function to order food could be identified as "orderFood". In most - * cases this identifier should come from the ID automatically generated by the AppFunctions - * SDK. You can determine the specific function to invoke by calling {@link - * ExecuteAppFunctionRequest#getFunctionIdentifier()}. - * - *

This method is always triggered in the main thread. You should run heavy tasks on a worker - * thread and dispatch the result with the given callback. You should always report back the - * result using the callback, no matter if the execution was successful or not. - * - *

This method also accepts a {@link CancellationSignal} that the app should listen to cancel - * the execution of function if requested by the system. - * - * @param request The function execution request. - * @param cancellationSignal A signal to cancel the execution. - * @param callback A callback to report back the result. - * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, String, - * CancellationSignal, Consumer)} instead. This method will be removed once usage references - * are updated. - */ - @MainThread - @Deprecated - public void onExecuteFunction( - @NonNull ExecuteAppFunctionRequest request, - @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer callback) { - onExecuteFunction(request, callback); - } - /** * Called by the system to execute a specific app function. * @@ -235,11 +167,9 @@ public abstract class AppFunctionService extends Service { * @param callback A callback to report back the result. */ @MainThread - public void onExecuteFunction( + public abstract void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull String callingPackage, @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer callback) { - onExecuteFunction(request, cancellationSignal, callback); - } + @NonNull Consumer callback); } diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index e9845c1d9f13..27817e9eb984 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -4,7 +4,6 @@ package com.google.android.appfunctions.sidecar { public final class AppFunctionManager { ctor public AppFunctionManager(android.content.Context); method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); - method @Deprecated public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0 @@ -15,9 +14,7 @@ package com.google.android.appfunctions.sidecar { public abstract class AppFunctionService extends android.app.Service { ctor public AppFunctionService(); method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); - method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); - method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer); + method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); 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"; } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java index d660926575d1..43377d8eb91c 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java @@ -125,33 +125,6 @@ public final class AppFunctionManager { }); } - /** - * Executes the app function. - * - *

Proxies request and response to the underlying {@link - * android.app.appfunctions.AppFunctionManager#executeAppFunction}, converting the request and - * response in the appropriate type required by the function. - * - * @deprecated Use {@link #executeAppFunction(ExecuteAppFunctionRequest, Executor, - * CancellationSignal, Consumer)} instead. This method will be removed once usage references - * are updated. - */ - @Deprecated - public void executeAppFunction( - @NonNull ExecuteAppFunctionRequest sidecarRequest, - @NonNull @CallbackExecutor Executor executor, - @NonNull Consumer callback) { - Objects.requireNonNull(sidecarRequest); - Objects.requireNonNull(executor); - Objects.requireNonNull(callback); - - executeAppFunction( - sidecarRequest, - executor, - new CancellationSignal(), - callback); - } - /** * Returns a boolean through a callback, indicating whether the app function is enabled. * diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java index 2a168e871713..0dc87e45b7e3 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java @@ -119,76 +119,9 @@ public abstract class AppFunctionService extends Service { * @param callback A callback to report back the result. */ @MainThread - public void onExecuteFunction( + public abstract void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull String callingPackage, @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer callback) { - onExecuteFunction(request, cancellationSignal, callback); - } - - /** - * Called by the system to execute a specific app function. - * - *

This method is triggered when the system requests your AppFunctionService to handle a - * particular function you have registered and made available. - * - *

To ensure proper routing of function requests, assign a unique identifier to each - * function. This identifier doesn't need to be globally unique, but it must be unique within - * your app. For example, a function to order food could be identified as "orderFood". In most - * cases this identifier should come from the ID automatically generated by the AppFunctions - * SDK. You can determine the specific function to invoke by calling {@link - * ExecuteAppFunctionRequest#getFunctionIdentifier()}. - * - *

This method is always triggered in the main thread. You should run heavy tasks on a worker - * thread and dispatch the result with the given callback. You should always report back the - * result using the callback, no matter if the execution was successful or not. - * - * @param request The function execution request. - * @param cancellationSignal A {@link CancellationSignal} to cancel the request. - * @param callback A callback to report back the result. - * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, String, - * CancellationSignal, Consumer)} instead. This method will be removed once usage references - * are updated. - */ - @MainThread - @Deprecated - public void onExecuteFunction( - @NonNull ExecuteAppFunctionRequest request, - @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer callback) { - onExecuteFunction(request, callback); - } - - /** - * Called by the system to execute a specific app function. - * - *

This method is triggered when the system requests your AppFunctionService to handle a - * particular function you have registered and made available. - * - *

To ensure proper routing of function requests, assign a unique identifier to each - * function. This identifier doesn't need to be globally unique, but it must be unique within - * your app. For example, a function to order food could be identified as "orderFood". In most - * cases this identifier should come from the ID automatically generated by the AppFunctions - * SDK. You can determine the specific function to invoke by calling {@link - * ExecuteAppFunctionRequest#getFunctionIdentifier()}. - * - *

This method is always triggered in the main thread. You should run heavy tasks on a worker - * thread and dispatch the result with the given callback. You should always report back the - * result using the callback, no matter if the execution was successful or not. - * - * @param request The function execution request. - * @param callback A callback to report back the result. - * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal, - * Consumer)} instead. This method will be removed once usage references are updated. - */ - @MainThread - @Deprecated - public void onExecuteFunction( - @NonNull ExecuteAppFunctionRequest request, - @NonNull Consumer callback) { - Log.w( - "AppFunctionService", - "Calling deprecated default implementation of onExecuteFunction"); - } + @NonNull Consumer callback); } -- cgit v1.2.3-59-g8ed1b From 8b1d9feea9a034c75eb86e7deb69fd74eb35b28b Mon Sep 17 00:00:00 2001 From: Oluwarotimi Adesina Date: Mon, 28 Oct 2024 16:14:35 +0000 Subject: Update AppFunction sidecar lib Documentation Flag: android.app.appfunctions.flags.enable_app_function_manager Test: Existing CTS Bug: 375121362 Change-Id: I565a875ed7966ce3b5f2b0e261ee26822bb0caca --- .../app/appfunctions/AppFunctionManager.java | 6 +-- .../appfunctions/sidecar/AppFunctionManager.java | 48 +++++----------------- .../sidecar/ExecuteAppFunctionRequest.java | 31 ++++++++++---- .../sidecar/ExecuteAppFunctionResponse.java | 11 ++--- 4 files changed, 41 insertions(+), 55 deletions(-) (limited to 'libs/appfunctions/java') diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java index 5b478d09ecdc..8944bb9c9c4c 100644 --- a/core/java/android/app/appfunctions/AppFunctionManager.java +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -47,10 +47,10 @@ import java.util.function.Consumer; *

An app function is a piece of functionality that apps expose to the system for cross-app * orchestration. * - *

**Developer Workflow:** + *

**Building App Functions:** * - *

Most developers should interact with app functions through the AppFunctions SDK. This SDK - * library offers a more convenient and type-safe way to represent the inputs and outputs of an app + *

Most developers should build app functions through the AppFunctions SDK. This SDK library + * offers a more convenient and type-safe way to represent the inputs and outputs of an app * function, using custom data classes called "AppFunction Schemas". * *

The suggested way to build an app function is to use the AppFunctions SDK. The SDK provides diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java index 43377d8eb91c..6870446fc3a0 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java @@ -16,7 +16,6 @@ package com.google.android.appfunctions.sidecar; -import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; @@ -41,8 +40,6 @@ import java.util.function.Consumer; *

This class wraps {@link android.app.appfunctions.AppFunctionManager} functionalities and * exposes it here as a sidecar library (avoiding direct dependency on the platform API). */ -// TODO(b/357551503): Implement get and set enabled app function APIs. -// TODO(b/367329899): Add sidecar library to Android B builds. public final class AppFunctionManager { /** * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset @@ -70,9 +67,9 @@ public final class AppFunctionManager { @IntDef( prefix = {"APP_FUNCTION_STATE_"}, value = { - APP_FUNCTION_STATE_DEFAULT, - APP_FUNCTION_STATE_ENABLED, - APP_FUNCTION_STATE_DISABLED + APP_FUNCTION_STATE_DEFAULT, + APP_FUNCTION_STATE_ENABLED, + APP_FUNCTION_STATE_DISABLED }) @Retention(RetentionPolicy.SOURCE) public @interface EnabledState {} @@ -102,6 +99,9 @@ public final class AppFunctionManager { *

Proxies request and response to the underlying {@link * android.app.appfunctions.AppFunctionManager#executeAppFunction}, converting the request and * response in the appropriate type required by the function. + * + *

See {@link android.app.appfunctions.AppFunctionManager#executeAppFunction} for the + * documented behaviour of this method. */ public void executeAppFunction( @NonNull ExecuteAppFunctionRequest sidecarRequest, @@ -128,24 +128,8 @@ public final class AppFunctionManager { /** * Returns a boolean through a callback, indicating whether the app function is enabled. * - *

* This method can only check app functions owned by the caller, or those where the caller - * has visibility to the owner package and holds either the {@link - * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link - * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission. - * - *

If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: - * - *

    - *
  • {@link IllegalArgumentException}, if the function is not found or the caller does not - * have access to it. - *
- * - * @param functionIdentifier the identifier of the app function to check (unique within the - * target package) and in most cases, these are automatically generated by the AppFunctions - * SDK - * @param targetPackage the package name of the app function's owner - * @param executor the executor to run the request - * @param callback the callback to receive the function enabled check result + *

See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the + * documented behaviour of this method. */ public void isAppFunctionEnabled( @NonNull String functionIdentifier, @@ -158,20 +142,8 @@ public final class AppFunctionManager { /** * Sets the enabled state of the app function owned by the calling package. * - *

If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: - * - *

    - *
  • {@link IllegalArgumentException}, if the function is not found or the caller does not - * have access to it. - *
- * - * @param functionIdentifier the identifier of the app function to enable (unique within the - * calling package). In most cases, identifiers are automatically generated by the - * AppFunctions SDK - * @param newEnabledState the new state of the app function - * @param executor the executor to run the callback - * @param callback the callback to receive the result of the function enablement. The call was - * successful if no exception was thrown. + *

See {@link android.app.appfunctions.AppFunctionManager#setAppFunctionEnabled} for the + * documented behavoir of this method. */ // Constants in @EnabledState should always mirror those in // android.app.appfunctions.AppFunctionManager. diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java index fa6d2ff12313..593c5213dd52 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java @@ -34,8 +34,8 @@ public final class ExecuteAppFunctionRequest { @NonNull private final String mTargetPackageName; /** - * Returns the unique string identifier of the app function to be executed. TODO(b/357551503): - * Document how callers can get the available function identifiers. + * The unique string identifier of the app function to be executed. This identifier is used to + * execute a specific app function. */ @NonNull private final String mFunctionIdentifier; @@ -49,8 +49,6 @@ public final class ExecuteAppFunctionRequest { * *

The document may have missing parameters. Developers are advised to implement defensive * handling measures. - * - *

TODO(b/357551503): Document how function parameters can be obtained for function execution */ @NonNull private final GenericDocument mParameters; @@ -71,7 +69,19 @@ public final class ExecuteAppFunctionRequest { return mTargetPackageName; } - /** Returns the unique string identifier of the app function to be executed. */ + /** + * Returns the unique string identifier of the app function to be executed. + * + *

When there is a package change or the device starts up, the metadata of available + * functions is indexed by AppSearch. AppSearch stores the indexed information as {@code + * AppFunctionStaticMetadata} document. + * + *

The ID can be obtained by querying the {@code AppFunctionStaticMetadata} documents from + * AppSearch. + * + *

If the {@code functionId} provided is invalid, the caller will get an invalid argument + * response. + */ @NonNull public String getFunctionIdentifier() { return mFunctionIdentifier; @@ -83,6 +93,12 @@ public final class ExecuteAppFunctionRequest { * *

The bundle may have missing parameters. Developers are advised to implement defensive * handling measures. + * + *

Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be + * obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This + * metadata will contain enough information for the caller to resolve the required parameters + * either using information from the metadata itself or using the AppFunction SDK for function + * callers. */ @NonNull public GenericDocument getParameters() { @@ -128,10 +144,7 @@ public final class ExecuteAppFunctionRequest { @NonNull public ExecuteAppFunctionRequest build() { return new ExecuteAppFunctionRequest( - mTargetPackageName, - mFunctionIdentifier, - mExtras, - mParameters); + mTargetPackageName, mFunctionIdentifier, mExtras, mParameters); } } } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java index 969e5d58b909..d5dfaeb77140 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java @@ -56,14 +56,15 @@ public final class ExecuteAppFunctionResponse { /** The caller does not have the permission to execute an app function. */ public static final int RESULT_DENIED = 1; - /** An unknown error occurred while processing the call in the AppFunctionService. */ - public static final int RESULT_APP_UNKNOWN_ERROR = 2; - /** - * An internal error occurred within AppFunctionManagerService. + * An unknown error occurred while processing the call in the AppFunctionService. * - *

This error may be considered similar to {@link IllegalStateException} + *

This error is thrown when the service is connected in the remote application but an + * unexpected error is thrown from the bound application. */ + public static final int RESULT_APP_UNKNOWN_ERROR = 2; + + /** An internal unexpected error coming from the system. */ public static final int RESULT_INTERNAL_ERROR = 3; /** -- cgit v1.2.3-59-g8ed1b From 3ead620641042b201e94d2b1af91af9e8445d406 Mon Sep 17 00:00:00 2001 From: Oluwarotimi Adesina Date: Mon, 28 Oct 2024 11:40:12 +0000 Subject: Add Error categories for AppFunction exe response Flag: android.app.appfunctions.flags.enable_app_function_manager Test: existing CTS Bug: 357551503 Change-Id: I9209234f28150ebde02886a1dddce0a218d43db0 --- core/api/current.txt | 17 ++- .../appfunctions/ExecuteAppFunctionResponse.java | 133 ++++++++++++++++++--- libs/appfunctions/api/current.txt | 17 ++- .../sidecar/ExecuteAppFunctionResponse.java | 133 ++++++++++++++++++--- 4 files changed, 260 insertions(+), 40 deletions(-) (limited to 'libs/appfunctions/java') diff --git a/core/api/current.txt b/core/api/current.txt index f26713178ca0..27c3cc5345d9 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8816,6 +8816,7 @@ package android.app.appfunctions { @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionResponse implements android.os.Parcelable { method public int describeContents(); + method public int getErrorCategory(); method @Nullable public String getErrorMessage(); method @NonNull public android.os.Bundle getExtras(); method public int getResultCode(); @@ -8825,13 +8826,17 @@ package android.app.appfunctions { 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 CREATOR; + field public static final int ERROR_CATEGORY_APP = 3; // 0x3 + field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1 + field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2 + field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 field public static final String PROPERTY_RETURN_VALUE = "returnValue"; - field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2 - field public static final int RESULT_CANCELLED = 6; // 0x6 - field public static final int RESULT_DENIED = 1; // 0x1 - field public static final int RESULT_DISABLED = 5; // 0x5 - field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3 - field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4 + 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_INTERNAL_ERROR = 2000; // 0x7d0 + field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9 field public static final int RESULT_OK = 0; // 0x0 } diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java index 83453a9d1c71..7e3409922a2b 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java @@ -73,38 +73,97 @@ public final class ExecuteAppFunctionResponse implements Parcelable { */ public static final String PROPERTY_RETURN_VALUE = "returnValue"; - /** The call was successful. */ + /** + * The call was successful. + * + *

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. */ - public static final int RESULT_DENIED = 1; + /** + * The caller does not have the permission to execute an app function. + * + *

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. + * + *

This error may be considered similar to {@link IllegalArgumentException}. + * + *

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. + * + *

This error is in the request error category. + * + *

This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int RESULT_DISABLED = 1002; + + /** + * An internal unexpected error coming from the system. + * + *

This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int RESULT_INTERNAL_ERROR = 2000; + + /** + * The operation was cancelled. Use this error code to report that a cancellation is done after + * receiving a cancellation signal. + * + *

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. * *

This error is thrown when the service is connected in the remote application but an * unexpected error is thrown from the bound application. + * + *

This error is in the {@link #ERROR_CATEGORY_APP} category. */ - public static final int RESULT_APP_UNKNOWN_ERROR = 2; + public static final int RESULT_APP_UNKNOWN_ERROR = 3000; - /** An internal unexpected error coming from the system. */ - public static final int RESULT_INTERNAL_ERROR = 3; + /** + * The error category is unknown. + * + *

This is the default value for {@link #getErrorCategory}. + */ + public static final int ERROR_CATEGORY_UNKNOWN = 0; /** - * The caller supplied invalid arguments to the call. + * The error is caused by the app requesting a function execution. * - *

This error may be considered similar to {@link IllegalArgumentException}. + *

For example, the caller provided invalid parameters in the execution request e.g. an + * invalid function ID. + * + *

Errors in the category fall in the range 1000-1999 inclusive. */ - public static final int RESULT_INVALID_ARGUMENT = 4; + public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; - /** The caller tried to execute a disabled app function. */ - public static final int RESULT_DISABLED = 5; + /** + * The error is caused by an issue in the system. + * + *

For example, the AppFunctionService implementation is not found by the system. + * + *

Errors in the category fall in the range 2000-2999 inclusive. + */ + public static final int ERROR_CATEGORY_SYSTEM = 2; /** - * The operation was cancelled. Use this error code to report that a cancellation is done after - * receiving a cancellation signal. + * The error is caused by the app providing the function. + * + *

For example, the app crashed when the system is executing the request. + * + *

Errors in the category fall in the range 3000-3999 inclusive. */ - public static final int RESULT_CANCELLED = 6; + public static final int ERROR_CATEGORY_APP = 3; /** The result code of the app function execution. */ @ResultCode private final int mResultCode; @@ -197,6 +256,36 @@ public final class ExecuteAppFunctionResponse implements Parcelable { return extras; } + /** + * Returns the error category of the {@link ExecuteAppFunctionResponse}. + * + *

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. + * + *

When constructing a {@link #newFailure} response, use the appropriate result code value to + * ensure correct categorization of the failed response. + * + *

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}. + * + *

See {@link ErrorCategory} for a complete list of error categories and their corresponding + * result code ranges. + */ + @ErrorCategory + public int getErrorCategory() { + if (mResultCode >= 1000 && mResultCode < 2000) { + return ERROR_CATEGORY_REQUEST_ERROR; + } + if (mResultCode >= 2000 && mResultCode < 3000) { + return ERROR_CATEGORY_SYSTEM; + } + if (mResultCode >= 3000 && mResultCode < 4000) { + return ERROR_CATEGORY_APP; + } + return ERROR_CATEGORY_UNKNOWN; + } + /** * Returns a generic document containing the return value of the executed function. * @@ -287,4 +376,20 @@ public final class ExecuteAppFunctionResponse implements Parcelable { }) @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/api/current.txt b/libs/appfunctions/api/current.txt index 27817e9eb984..6c42bd3998e5 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -34,6 +34,7 @@ package com.google.android.appfunctions.sidecar { } public final class ExecuteAppFunctionResponse { + method public int getErrorCategory(); method @Nullable public String getErrorMessage(); method @NonNull public android.os.Bundle getExtras(); method public int getResultCode(); @@ -41,13 +42,17 @@ package com.google.android.appfunctions.sidecar { method public boolean isSuccess(); method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle); method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle); + field public static final int ERROR_CATEGORY_APP = 3; // 0x3 + field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1 + field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2 + field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 field public static final String PROPERTY_RETURN_VALUE = "returnValue"; - field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2 - field public static final int RESULT_CANCELLED = 6; // 0x6 - field public static final int RESULT_DENIED = 1; // 0x1 - field public static final int RESULT_DISABLED = 5; // 0x5 - field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3 - field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4 + 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_INTERNAL_ERROR = 2000; // 0x7d0 + field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9 field public static final int RESULT_OK = 0; // 0x0 } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java index d5dfaeb77140..bf4ab45ed939 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java @@ -50,38 +50,97 @@ public final class ExecuteAppFunctionResponse { */ public static final String PROPERTY_RETURN_VALUE = "returnValue"; - /** The call was successful. */ + /** + * The call was successful. + * + *

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. */ - public static final int RESULT_DENIED = 1; + /** + * The caller does not have the permission to execute an app function. + * + *

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. + * + *

This error may be considered similar to {@link IllegalArgumentException}. + * + *

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. + * + *

This error is in the request error category. + * + *

This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int RESULT_DISABLED = 1002; + + /** + * An internal unexpected error coming from the system. + * + *

This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int RESULT_INTERNAL_ERROR = 2000; + + /** + * The operation was cancelled. Use this error code to report that a cancellation is done after + * receiving a cancellation signal. + * + *

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. * *

This error is thrown when the service is connected in the remote application but an * unexpected error is thrown from the bound application. + * + *

This error is in the {@link #ERROR_CATEGORY_APP} category. */ - public static final int RESULT_APP_UNKNOWN_ERROR = 2; + public static final int RESULT_APP_UNKNOWN_ERROR = 3000; - /** An internal unexpected error coming from the system. */ - public static final int RESULT_INTERNAL_ERROR = 3; + /** + * The error category is unknown. + * + *

This is the default value for {@link #getErrorCategory}. + */ + public static final int ERROR_CATEGORY_UNKNOWN = 0; /** - * The caller supplied invalid arguments to the call. + * The error is caused by the app requesting a function execution. * - *

This error may be considered similar to {@link IllegalArgumentException}. + *

For example, the caller provided invalid parameters in the execution request e.g. an + * invalid function ID. + * + *

Errors in the category fall in the range 1000-1999 inclusive. */ - public static final int RESULT_INVALID_ARGUMENT = 4; + public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; - /** The caller tried to execute a disabled app function. */ - public static final int RESULT_DISABLED = 5; + /** + * The error is caused by an issue in the system. + * + *

For example, the AppFunctionService implementation is not found by the system. + * + *

Errors in the category fall in the range 2000-2999 inclusive. + */ + public static final int ERROR_CATEGORY_SYSTEM = 2; /** - * The operation was cancelled. Use this error code to report that a cancellation is done after - * receiving a cancellation signal. + * The error is caused by the app providing the function. + * + *

For example, the app crashed when the system is executing the request. + * + *

Errors in the category fall in the range 3000-3999 inclusive. */ - public static final int RESULT_CANCELLED = 6; + public static final int ERROR_CATEGORY_APP = 3; /** The result code of the app function execution. */ @ResultCode private final int mResultCode; @@ -170,6 +229,36 @@ public final class ExecuteAppFunctionResponse { return extras; } + /** + * Returns the error category of the {@link ExecuteAppFunctionResponse}. + * + *

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. + * + *

When constructing a {@link #newFailure} response, use the appropriate result code value to + * ensure correct categorization of the failed response. + * + *

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}. + * + *

See {@link ErrorCategory} for a complete list of error categories and their corresponding + * result code ranges. + */ + @ErrorCategory + public int getErrorCategory() { + if (mResultCode >= 1000 && mResultCode < 2000) { + return ERROR_CATEGORY_REQUEST_ERROR; + } + if (mResultCode >= 2000 && mResultCode < 3000) { + return ERROR_CATEGORY_SYSTEM; + } + if (mResultCode >= 3000 && mResultCode < 4000) { + return ERROR_CATEGORY_APP; + } + return ERROR_CATEGORY_UNKNOWN; + } + /** * Returns a generic document containing the return value of the executed function. * @@ -245,4 +334,20 @@ public final class ExecuteAppFunctionResponse { }) @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 {} } -- cgit v1.2.3-59-g8ed1b From 67167c3b4524e709fda6baa67a98612a0f4d2cad Mon Sep 17 00:00:00 2001 From: Oluwarotimi Adesina Date: Wed, 30 Oct 2024 20:18:25 +0000 Subject: Split the isAppFunctionEnabled method into two overloads Flag: android.app.appfunctions.flags.enable_app_function_manager Test: CTS Bug: 376426049 Change-Id: I94c7a73983fea746dbf0815c77cc5e3705974c5d --- core/api/current.txt | 3 +- .../app/appfunctions/AppFunctionManager.java | 87 ++++++++++++++++------ libs/appfunctions/api/current.txt | 5 +- .../appfunctions/sidecar/AppFunctionManager.java | 27 +++++++ 4 files changed, 97 insertions(+), 25 deletions(-) (limited to 'libs/appfunctions/java') diff --git a/core/api/current.txt b/core/api/current.txt index 27c3cc5345d9..fa2060ce77f8 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8783,7 +8783,8 @@ package android.app.appfunctions { @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); - method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); + 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); + method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0 field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2 diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java index 74cae07d8911..5ddb590add4c 100644 --- a/core/java/android/app/appfunctions/AppFunctionManager.java +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -53,8 +53,8 @@ import java.util.function.Consumer; * offers a more convenient and type-safe way to build app functions. The SDK provides predefined * function schemas for common use cases and associated data classes for function parameters and * return values. Apps only have to implement the provided interfaces. Internally, the SDK converts - * these data classes into {@link ExecuteAppFunctionRequest#getParameters()} and - * {@link ExecuteAppFunctionResponse#getResultDocument()}. + * these data classes into {@link ExecuteAppFunctionRequest#getParameters()} and {@link + * ExecuteAppFunctionResponse#getResultDocument()}. * *

**Discovering App Functions:** * @@ -69,13 +69,12 @@ import java.util.function.Consumer; *

**Executing App Functions:** * *

To execute an app function, the caller app can retrieve the {@code functionIdentifier} from - * the {@code AppFunctionStaticMetadata} document and use it to build an - * {@link ExecuteAppFunctionRequest}. Then, invoke {@link #executeAppFunction} with the request - * to execute the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS} - * or {@code android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} permission to execute app - * functions from other apps. An app can always execute its own app functions and doesn't need these - * permissions. AppFunction SDK provides a convenient way to achieve this and is the preferred - * method. + * the {@code AppFunctionStaticMetadata} document and use it to build an {@link + * ExecuteAppFunctionRequest}. Then, invoke {@link #executeAppFunction} with the request to execute + * the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS} or {@code + * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} permission to execute app functions from other + * apps. An app can always execute its own app functions and doesn't need these permissions. + * AppFunction SDK provides a convenient way to achieve this and is the preferred method. * *

**Example:** * @@ -213,12 +212,13 @@ public final class AppFunctionManager { /** * Returns a boolean through a callback, indicating whether the app function is enabled. * - *

* This method can only check app functions owned by the caller, or those where the caller + *

This method can only check app functions owned by the caller, or those where the caller * has visibility to the owner package and holds either the {@link * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission. * - *

If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: + *

If the operation fails, the callback's {@link OutcomeReceiver#onError} is called with + * errors: * *

    *
  • {@link IllegalArgumentException}, if the function is not found or the caller does not @@ -232,23 +232,47 @@ public final class AppFunctionManager { * @param executor the executor to run the request * @param callback the callback to receive the function enabled check result */ + @RequiresPermission( + anyOf = { + Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, + Manifest.permission.EXECUTE_APP_FUNCTIONS + }, + conditional = true) public void isAppFunctionEnabled( @NonNull String functionIdentifier, @NonNull String targetPackage, @NonNull Executor executor, @NonNull OutcomeReceiver callback) { - Objects.requireNonNull(functionIdentifier); - Objects.requireNonNull(targetPackage); - Objects.requireNonNull(executor); - Objects.requireNonNull(callback); - AppSearchManager appSearchManager = mContext.getSystemService(AppSearchManager.class); - if (appSearchManager == null) { - callback.onError(new IllegalStateException("Failed to get AppSearchManager.")); - return; - } + isAppFunctionEnabledInternal(functionIdentifier, targetPackage, executor, callback); + } - AppFunctionManagerHelper.isAppFunctionEnabled( - functionIdentifier, targetPackage, appSearchManager, executor, callback); + /** + * Returns a boolean through a callback, indicating whether the app function is enabled. + * + *

    This method can only check app functions owned by the caller, unlike {@link + * #isAppFunctionEnabled(String, String, Executor, OutcomeReceiver)}, which allows specifying a + * different target package. + * + *

    If the operation fails, the callback's {@link OutcomeReceiver#onError} is called with + * errors: + * + *

      + *
    • {@link IllegalArgumentException}, if the function is not found or the caller does not + * have access to it. + *
    + * + * @param functionIdentifier the identifier of the app function to check (unique within the + * target package) and in most cases, these are automatically generated by the AppFunctions + * SDK + * @param executor the executor to run the request + * @param callback the callback to receive the function enabled check result + */ + public void isAppFunctionEnabled( + @NonNull String functionIdentifier, + @NonNull Executor executor, + @NonNull OutcomeReceiver callback) { + isAppFunctionEnabledInternal( + functionIdentifier, mContext.getPackageName(), executor, callback); } /** @@ -291,6 +315,25 @@ public final class AppFunctionManager { } } + private void isAppFunctionEnabledInternal( + @NonNull String functionIdentifier, + @NonNull String targetPackage, + @NonNull Executor executor, + @NonNull OutcomeReceiver callback) { + Objects.requireNonNull(functionIdentifier); + Objects.requireNonNull(targetPackage); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + AppSearchManager appSearchManager = mContext.getSystemService(AppSearchManager.class); + if (appSearchManager == null) { + callback.onError(new IllegalStateException("Failed to get AppSearchManager.")); + return; + } + + AppFunctionManagerHelper.isAppFunctionEnabled( + functionIdentifier, targetPackage, appSearchManager, executor, callback); + } + private static class CallbackWrapper extends IAppFunctionEnabledCallback.Stub { private final OutcomeReceiver mCallback; diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index 6c42bd3998e5..89cdba8ffec0 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -3,8 +3,9 @@ package com.google.android.appfunctions.sidecar { public final class AppFunctionManager { ctor public AppFunctionManager(android.content.Context); - method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); - method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); + method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); + 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); + method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0 field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2 diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java index 6870446fc3a0..2075104ff868 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java @@ -16,9 +16,11 @@ package com.google.android.appfunctions.sidecar; +import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.UserHandleAware; import android.content.Context; @@ -103,6 +105,12 @@ public final class AppFunctionManager { *

    See {@link android.app.appfunctions.AppFunctionManager#executeAppFunction} for the * documented behaviour of this method. */ + @RequiresPermission( + anyOf = { + Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, + Manifest.permission.EXECUTE_APP_FUNCTIONS + }, + conditional = true) public void executeAppFunction( @NonNull ExecuteAppFunctionRequest sidecarRequest, @NonNull @CallbackExecutor Executor executor, @@ -131,6 +139,12 @@ public final class AppFunctionManager { *

    See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the * documented behaviour of this method. */ + @RequiresPermission( + anyOf = { + Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, + Manifest.permission.EXECUTE_APP_FUNCTIONS + }, + conditional = true) public void isAppFunctionEnabled( @NonNull String functionIdentifier, @NonNull String targetPackage, @@ -139,6 +153,19 @@ public final class AppFunctionManager { mManager.isAppFunctionEnabled(functionIdentifier, targetPackage, executor, callback); } + /** + * Returns a boolean through a callback, indicating whether the app function is enabled. + * + *

    See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the + * documented behaviour of this method. + */ + public void isAppFunctionEnabled( + @NonNull String functionIdentifier, + @NonNull Executor executor, + @NonNull OutcomeReceiver callback) { + mManager.isAppFunctionEnabled(functionIdentifier, executor, callback); + } + /** * Sets the enabled state of the app function owned by the calling package. * -- cgit v1.2.3-59-g8ed1b From e97cf01874df6b8b1177e1cc8d062327614939ff Mon Sep 17 00:00:00 2001 From: Oluwarotimi Adesina Date: Wed, 30 Oct 2024 21:45:00 +0000 Subject: Add a dedicated error code for missing function ID Flag: android.app.appfunctions.flags.enable_app_function_manager Test: CTS Bug: 375121362 Change-Id: I917df5c09090e549dade1c666e3d2ccaa8e8ac92 --- core/api/current.txt | 3 ++- .../app/appfunctions/ExecuteAppFunctionResponse.java | 14 ++++++++++---- libs/appfunctions/api/current.txt | 3 ++- .../sidecar/ExecuteAppFunctionResponse.java | 14 ++++++++++---- .../sidecar/tests/SidecarConverterTest.kt | 8 ++++---- .../appfunctions/AppFunctionManagerServiceImpl.java | 20 ++++++++++---------- 6 files changed, 38 insertions(+), 24 deletions(-) (limited to 'libs/appfunctions/java') diff --git a/core/api/current.txt b/core/api/current.txt index fa2060ce77f8..522d2f28a6fc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8836,9 +8836,10 @@ package android.app.appfunctions { 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_INTERNAL_ERROR = 2000; // 0x7d0 + 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/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java index 7e3409922a2b..cdf02e6f5a09 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java @@ -99,18 +99,23 @@ public final class ExecuteAppFunctionResponse implements Parcelable { /** * The caller tried to execute a disabled app function. * - *

    This error is in the request error category. - * *

    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. + * + *

    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. * *

    This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. */ - public static final int RESULT_INTERNAL_ERROR = 2000; + public static final int RESULT_SYSTEM_ERROR = 2000; /** * The operation was cancelled. Use this error code to report that a cancellation is done after @@ -369,7 +374,8 @@ public final class ExecuteAppFunctionResponse implements Parcelable { RESULT_OK, RESULT_DENIED, RESULT_APP_UNKNOWN_ERROR, - RESULT_INTERNAL_ERROR, + RESULT_FUNCTION_NOT_FOUND, + RESULT_SYSTEM_ERROR, RESULT_INVALID_ARGUMENT, RESULT_DISABLED, RESULT_CANCELLED diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index 89cdba8ffec0..faf84a8ab5ac 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -52,9 +52,10 @@ package com.google.android.appfunctions.sidecar { 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_INTERNAL_ERROR = 2000; // 0x7d0 + 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/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java index bf4ab45ed939..4e88fb025a9d 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java @@ -76,18 +76,23 @@ public final class ExecuteAppFunctionResponse { /** * The caller tried to execute a disabled app function. * - *

    This error is in the request error category. - * *

    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. + * + *

    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. * *

    This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. */ - public static final int RESULT_INTERNAL_ERROR = 2000; + public static final int RESULT_SYSTEM_ERROR = 2000; /** * The operation was cancelled. Use this error code to report that a cancellation is done after @@ -327,7 +332,8 @@ public final class ExecuteAppFunctionResponse { RESULT_OK, RESULT_DENIED, RESULT_APP_UNKNOWN_ERROR, - RESULT_INTERNAL_ERROR, + RESULT_SYSTEM_ERROR, + RESULT_FUNCTION_NOT_FOUND, RESULT_INVALID_ARGUMENT, RESULT_DISABLED, RESULT_CANCELLED diff --git a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt index 1f9fddd3c1ec..264f84209caf 100644 --- a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt +++ b/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt @@ -105,7 +105,7 @@ class SidecarConverterTest { val emptyGd = GenericDocument.Builder>("", "", "").build() val platformResponse = ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR, + ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, null, null ) @@ -119,7 +119,7 @@ class SidecarConverterTest { assertThat(sidecarResponse.resultDocument.id).isEqualTo(emptyGd.id) assertThat(sidecarResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType) assertThat(sidecarResponse.resultCode) - .isEqualTo(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR) + .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR) assertThat(sidecarResponse.errorMessage).isNull() } @@ -152,7 +152,7 @@ class SidecarConverterTest { val emptyGd = GenericDocument.Builder>("", "", "").build() val sidecarResponse = com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR, + ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, null, null ) @@ -166,7 +166,7 @@ class SidecarConverterTest { assertThat(platformResponse.resultDocument.id).isEqualTo(emptyGd.id) assertThat(platformResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType) assertThat(platformResponse.resultCode) - .isEqualTo(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR) + .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR) assertThat(platformResponse.errorMessage).isNull() } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index 89f14b09d397..268e56487c4b 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -86,7 +86,6 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { private final Context mContext; private final Map mLocks = new WeakHashMap<>(); - public AppFunctionManagerServiceImpl(@NonNull Context context) { this( context, @@ -201,7 +200,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { if (mCallerValidator.isUserOrganizationManaged(targetUser)) { safeExecuteAppFunctionCallback.onResult( ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR, + ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, "Cannot run on a device with a device owner or from the managed" + " profile.", /* extras= */ null)); @@ -256,7 +255,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { if (serviceIntent == null) { safeExecuteAppFunctionCallback.onResult( ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR, + ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, "Cannot find the target service.", /* extras= */ null)); return; @@ -449,7 +448,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { Slog.e(TAG, "Failed to bind to the AppFunctionService"); safeExecuteAppFunctionCallback.onResult( ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR, + ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, "Failed to bind the AppFunctionService.", /* extras= */ null)); } @@ -464,7 +463,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { if (e instanceof CompletionException) { e = e.getCause(); } - int resultCode = ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR; + int resultCode = ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR; if (e instanceof AppSearchException appSearchException) { resultCode = mapAppSearchResultFailureCodeToExecuteAppFunctionResponse( @@ -486,13 +485,13 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { switch (resultCode) { case AppSearchResult.RESULT_NOT_FOUND: - return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT; + return ExecuteAppFunctionResponse.RESULT_FUNCTION_NOT_FOUND; case AppSearchResult.RESULT_INVALID_ARGUMENT: case AppSearchResult.RESULT_INTERNAL_ERROR: case AppSearchResult.RESULT_SECURITY_ERROR: // fall-through } - return ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR; + return ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR; } private void registerAppSearchObserver(@NonNull TargetUser user) { @@ -543,12 +542,13 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { }); } } + /** * Retrieves the lock object associated with the given package name. * - * This method returns the lock object from the {@code mLocks} map if it exists. - * If no lock is found for the given package name, a new lock object is created, - * stored in the map, and returned. + *

    This method returns the lock object from the {@code mLocks} map if it exists. If no lock + * is found for the given package name, a new lock object is created, stored in the map, and + * returned. */ @VisibleForTesting @NonNull -- cgit v1.2.3-59-g8ed1b From 08c469d59be6c9e83cd0d2b9a98740e902cf6032 Mon Sep 17 00:00:00 2001 From: Oluwarotimi Adesina Date: Wed, 6 Nov 2024 16:57:40 +0000 Subject: API Council feedback Flag: android.app.appfunctions.flags.enable_app_function_manager Test: CTS Bug: 376890974 Change-Id: Idc835c2af581a0f60a2c64d2ffafbbb1a353356a --- core/api/current.txt | 2 +- core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java | 4 ++-- core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java | 2 +- libs/appfunctions/api/current.txt | 2 +- .../android/appfunctions/sidecar/ExecuteAppFunctionRequest.java | 4 ++-- .../android/appfunctions/sidecar/ExecuteAppFunctionResponse.java | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) (limited to 'libs/appfunctions/java') diff --git a/core/api/current.txt b/core/api/current.txt index ead655426bf9..42d4c145343c 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8837,7 +8837,7 @@ package android.app.appfunctions { field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1 field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2 field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 - field public static final String PROPERTY_RETURN_VALUE = "returnValue"; + field public static final 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 diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java index 41bb62270e9f..1557815a8468 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java @@ -111,8 +111,8 @@ public final class ExecuteAppFunctionRequest implements Parcelable { * Returns the function parameters. The key is the parameter name, and the value is the * parameter value. * - *

    The bundle may have missing parameters. Developers are advised to implement defensive - * handling measures. + *

    The {@link GenericDocument} may have missing parameters. Developers are advised to + * implement defensive handling measures. * * @see AppFunctionManager on how to determine the expected parameters. */ diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java index cdf02e6f5a09..ced4b553d641 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java @@ -71,7 +71,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable { * *

    See {@link #getResultDocument} for more information on extracting the return value. */ - public static final String PROPERTY_RETURN_VALUE = "returnValue"; + public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; /** * The call was successful. diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index faf84a8ab5ac..0f384e948575 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -47,7 +47,7 @@ package com.google.android.appfunctions.sidecar { field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1 field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2 field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 - field public static final String PROPERTY_RETURN_VALUE = "returnValue"; + field public static final 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 diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java index 593c5213dd52..9cf4c6a9b3c5 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java @@ -91,8 +91,8 @@ public final class ExecuteAppFunctionRequest { * Returns the function parameters. The key is the parameter name, and the value is the * parameter value. * - *

    The bundle may have missing parameters. Developers are advised to implement defensive - * handling measures. + *

    The {@link GenericDocument} may have missing parameters. Developers are advised to + * implement defensive handling measures. * *

    Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be * obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java index 4e88fb025a9d..c806a0780551 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java @@ -48,7 +48,7 @@ public final class ExecuteAppFunctionResponse { * *

    See {@link #getResultDocument} for more information on extracting the return value. */ - public static final String PROPERTY_RETURN_VALUE = "returnValue"; + public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; /** * The call was successful. -- cgit v1.2.3-59-g8ed1b From c0c499e18c84cdc04136201782fb455c53a753a2 Mon Sep 17 00:00:00 2001 From: Oluwarotimi Adesina Date: Wed, 6 Nov 2024 18:14:54 +0000 Subject: remame appfunction sidecar jar Flag: android.app.appfunctions.flags.enable_app_function_manager Test: cts Bug: 357551503 Change-Id: I19b5d9a0856429ad9b072aaa3f9b8e5a0dfde063 --- libs/appfunctions/Android.bp | 8 +- libs/appfunctions/api/current.txt | 16 +- libs/appfunctions/appfunctions.extension.xml | 21 ++ libs/appfunctions/appfunctions.sidecar.xml | 21 -- .../appfunctions/AppFunctionManager.java | 186 +++++++++++ .../appfunctions/AppFunctionService.java | 126 ++++++++ .../appfunctions/ExecuteAppFunctionRequest.java | 150 +++++++++ .../appfunctions/ExecuteAppFunctionResponse.java | 359 +++++++++++++++++++++ .../extensions/appfunctions/SidecarConverter.java | 97 ++++++ .../appfunctions/sidecar/AppFunctionManager.java | 186 ----------- .../appfunctions/sidecar/AppFunctionService.java | 127 -------- .../sidecar/ExecuteAppFunctionRequest.java | 150 --------- .../sidecar/ExecuteAppFunctionResponse.java | 359 --------------------- .../appfunctions/sidecar/SidecarConverter.java | 104 ------ libs/appfunctions/tests/Android.bp | 2 +- .../appfunctions/tests/SidecarConverterTest.kt | 175 ++++++++++ .../sidecar/tests/SidecarConverterTest.kt | 172 ---------- 17 files changed, 1127 insertions(+), 1132 deletions(-) create mode 100644 libs/appfunctions/appfunctions.extension.xml delete mode 100644 libs/appfunctions/appfunctions.sidecar.xml create mode 100644 libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java create mode 100644 libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java create mode 100644 libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java create mode 100644 libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java create mode 100644 libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java delete mode 100644 libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java delete mode 100644 libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java delete mode 100644 libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java delete mode 100644 libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java delete mode 100644 libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java create mode 100644 libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt delete mode 100644 libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt (limited to 'libs/appfunctions/java') diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp index c6cee07d1946..5ab5a7a59c2a 100644 --- a/libs/appfunctions/Android.bp +++ b/libs/appfunctions/Android.bp @@ -18,10 +18,10 @@ package { } java_sdk_library { - name: "com.google.android.appfunctions.sidecar", + name: "com.android.extensions.appfunctions", owner: "google", srcs: ["java/**/*.java"], - api_packages: ["com.google.android.appfunctions.sidecar"], + api_packages: ["com.android.extensions.appfunctions"], dex_preopt: { enabled: false, }, @@ -31,9 +31,9 @@ java_sdk_library { } prebuilt_etc { - name: "appfunctions.sidecar.xml", + name: "appfunctions.extension.xml", system_ext_specific: true, sub_dir: "permissions", - src: "appfunctions.sidecar.xml", + src: "appfunctions.extension.xml", filename_from_src: true, } diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index 0f384e948575..0eda10112ee8 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -1,9 +1,9 @@ // Signature format: 2.0 -package com.google.android.appfunctions.sidecar { +package com.android.extensions.appfunctions { public final class AppFunctionManager { ctor public AppFunctionManager(android.content.Context); - method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); + 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); 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); method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); @@ -15,7 +15,7 @@ package com.google.android.appfunctions.sidecar { public abstract class AppFunctionService extends android.app.Service { ctor public AppFunctionService(); method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); + method @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE"; field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } @@ -29,9 +29,9 @@ package com.google.android.appfunctions.sidecar { public static final class ExecuteAppFunctionRequest.Builder { ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String); - method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest build(); - method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle); - method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument); + method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest build(); + method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument); } public final class ExecuteAppFunctionResponse { @@ -41,8 +41,8 @@ package com.google.android.appfunctions.sidecar { method public int getResultCode(); method @NonNull public android.app.appsearch.GenericDocument getResultDocument(); method public boolean isSuccess(); - method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle); - method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle); + 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 diff --git a/libs/appfunctions/appfunctions.extension.xml b/libs/appfunctions/appfunctions.extension.xml new file mode 100644 index 000000000000..dd09cc39d12f --- /dev/null +++ b/libs/appfunctions/appfunctions.extension.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/libs/appfunctions/appfunctions.sidecar.xml b/libs/appfunctions/appfunctions.sidecar.xml deleted file mode 100644 index bef8b6ec7ce6..000000000000 --- a/libs/appfunctions/appfunctions.sidecar.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - \ No newline at end of file diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java new file mode 100644 index 000000000000..d64593d8ff5f --- /dev/null +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java @@ -0,0 +1,186 @@ +/* + * 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.Manifest; +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; +import android.annotation.UserHandleAware; +import android.content.Context; +import android.os.CancellationSignal; +import android.os.OutcomeReceiver; + +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. + * + *

    App function is a specific piece of functionality that an app offers to the system. These + * functionalities can be integrated into various system features. + * + *

    This class wraps {@link android.app.appfunctions.AppFunctionManager} functionalities and + * exposes it here as a sidecar library (avoiding direct dependency on the platform API). + */ +public final class AppFunctionManager { + /** + * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset + * enabled state to the default value. + */ + public static final int APP_FUNCTION_STATE_DEFAULT = 0; + + /** + * The app function is enabled. To enable an app function, call {@link #setAppFunctionEnabled} + * with this value. + */ + public static final int APP_FUNCTION_STATE_ENABLED = 1; + + /** + * The app function is disabled. To disable an app function, call {@link #setAppFunctionEnabled} + * with this value. + */ + public static final int APP_FUNCTION_STATE_DISABLED = 2; + + /** + * The enabled state of the app function. + * + * @hide + */ + @IntDef( + prefix = {"APP_FUNCTION_STATE_"}, + value = { + APP_FUNCTION_STATE_DEFAULT, + APP_FUNCTION_STATE_ENABLED, + APP_FUNCTION_STATE_DISABLED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EnabledState {} + + private final android.app.appfunctions.AppFunctionManager mManager; + private final Context mContext; + + /** + * Creates an instance. + * + * @param context A {@link Context}. + * @throws java.lang.IllegalStateException if the underlying {@link + * android.app.appfunctions.AppFunctionManager} is not found. + */ + public AppFunctionManager(Context context) { + mContext = Objects.requireNonNull(context); + mManager = context.getSystemService(android.app.appfunctions.AppFunctionManager.class); + if (mManager == null) { + throw new IllegalStateException( + "Underlying AppFunctionManager system service not found."); + } + } + + /** + * Executes the app function. + * + *

    Proxies request and response to the underlying {@link + * android.app.appfunctions.AppFunctionManager#executeAppFunction}, converting the request and + * response in the appropriate type required by the function. + * + *

    See {@link android.app.appfunctions.AppFunctionManager#executeAppFunction} for the + * documented behaviour of this method. + */ + @RequiresPermission( + anyOf = { + Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, + Manifest.permission.EXECUTE_APP_FUNCTIONS + }, + conditional = true) + public void executeAppFunction( + @NonNull ExecuteAppFunctionRequest sidecarRequest, + @NonNull @CallbackExecutor Executor executor, + @NonNull CancellationSignal cancellationSignal, + @NonNull Consumer callback) { + Objects.requireNonNull(sidecarRequest); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + android.app.appfunctions.ExecuteAppFunctionRequest platformRequest = + SidecarConverter.getPlatformExecuteAppFunctionRequest(sidecarRequest); + mManager.executeAppFunction( + platformRequest, + executor, + cancellationSignal, + (platformResponse) -> { + callback.accept( + SidecarConverter.getSidecarExecuteAppFunctionResponse( + platformResponse)); + }); + } + + /** + * Returns a boolean through a callback, indicating whether the app function is enabled. + * + *

    See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the + * documented behaviour of this method. + */ + @RequiresPermission( + anyOf = { + Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, + Manifest.permission.EXECUTE_APP_FUNCTIONS + }, + conditional = true) + public void isAppFunctionEnabled( + @NonNull String functionIdentifier, + @NonNull String targetPackage, + @NonNull Executor executor, + @NonNull OutcomeReceiver callback) { + mManager.isAppFunctionEnabled(functionIdentifier, targetPackage, executor, callback); + } + + /** + * Returns a boolean through a callback, indicating whether the app function is enabled. + * + *

    See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the + * documented behaviour of this method. + */ + public void isAppFunctionEnabled( + @NonNull String functionIdentifier, + @NonNull Executor executor, + @NonNull OutcomeReceiver callback) { + mManager.isAppFunctionEnabled(functionIdentifier, executor, callback); + } + + /** + * Sets the enabled state of the app function owned by the calling package. + * + *

    See {@link android.app.appfunctions.AppFunctionManager#setAppFunctionEnabled} for the + * documented behavoir of this method. + */ + // Constants in @EnabledState should always mirror those in + // android.app.appfunctions.AppFunctionManager. + @SuppressLint("WrongConstant") + @UserHandleAware + public void setAppFunctionEnabled( + @NonNull String functionIdentifier, + @EnabledState int newEnabledState, + @NonNull Executor executor, + @NonNull OutcomeReceiver callback) { + mManager.setAppFunctionEnabled(functionIdentifier, newEnabledState, executor, callback); + } +} diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java new file mode 100644 index 000000000000..1a4d9da8bd63 --- /dev/null +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.extensions.appfunctions; + +import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE; + +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.CancellationSignal; +import android.os.IBinder; + +import java.util.function.Consumer; + +/** + * Abstract base class to provide app functions to the system. + * + *

    Include the following in the manifest: + * + *

    + * {@literal
    + * 
    + *    
    + *      
    + *    
    + * 
    + * }
    + * 
    + * + *

    This class wraps {@link android.app.appfunctions.AppFunctionService} functionalities and + * exposes it here as a sidecar library (avoiding direct dependency on the platform API). + * + * @see AppFunctionManager + */ +public abstract class AppFunctionService extends Service { + /** + * The permission to only allow system access to the functions through {@link + * AppFunctionManagerService}. + */ + @NonNull + public static final String BIND_APP_FUNCTION_SERVICE = + "android.permission.BIND_APP_FUNCTION_SERVICE"; + + /** + * The {@link Intent} that must be declared as handled by the service. To be supported, the + * service must also require the {@link BIND_APP_FUNCTION_SERVICE} permission so that other + * applications can not abuse it. + */ + @NonNull + public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; + + private final Binder mBinder = + android.app.appfunctions.AppFunctionService.createBinder( + /* context= */ this, + /* onExecuteFunction= */ (platformRequest, + callingPackage, + cancellationSignal, + callback) -> { + AppFunctionService.this.onExecuteFunction( + SidecarConverter.getSidecarExecuteAppFunctionRequest( + platformRequest), + callingPackage, + cancellationSignal, + (sidecarResponse) -> { + callback.accept( + SidecarConverter.getPlatformExecuteAppFunctionResponse( + sidecarResponse)); + }); + }); + + @NonNull + @Override + public final IBinder onBind(@Nullable Intent intent) { + return mBinder; + } + + /** + * Called by the system to execute a specific app function. + * + *

    This method is triggered when the system requests your AppFunctionService to handle a + * particular function you have registered and made available. + * + *

    To ensure proper routing of function requests, assign a unique identifier to each + * function. This identifier doesn't need to be globally unique, but it must be unique within + * your app. For example, a function to order food could be identified as "orderFood". In most + * cases this identifier should come from the ID automatically generated by the AppFunctions + * SDK. You can determine the specific function to invoke by calling {@link + * ExecuteAppFunctionRequest#getFunctionIdentifier()}. + * + *

    This method is always triggered in the main thread. You should run heavy tasks on a worker + * thread and dispatch the result with the given callback. You should always report back the + * result using the callback, no matter if the execution was successful or not. + * + *

    This method also accepts a {@link CancellationSignal} that the app should listen to cancel + * the execution of function if requested by the system. + * + * @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. + */ + @MainThread + public abstract void onExecuteFunction( + @NonNull ExecuteAppFunctionRequest request, + @NonNull String callingPackage, + @NonNull CancellationSignal cancellationSignal, + @NonNull Consumer callback); +} diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java new file mode 100644 index 000000000000..baddc245f0f1 --- /dev/null +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.extensions.appfunctions; + +import android.annotation.NonNull; +import android.app.appsearch.GenericDocument; +import android.os.Bundle; + +import java.util.Objects; + +/** + * A request to execute an app function. + * + *

    This class copies {@link android.app.appfunctions.ExecuteAppFunctionRequest} without parcel + * functionality and exposes it here as a sidecar library (avoiding direct dependency on the + * platform API). + */ +public final class ExecuteAppFunctionRequest { + /** Returns the package name of the app that hosts the function. */ + @NonNull private final String mTargetPackageName; + + /** + * The unique string identifier of the app function to be executed. This identifier is used to + * execute a specific app function. + */ + @NonNull private final String mFunctionIdentifier; + + /** Returns additional metadata relevant to this function execution request. */ + @NonNull private final Bundle mExtras; + + /** + * Returns the parameters required to invoke this function. Within this [GenericDocument], the + * property names are the names of the function parameters and the property values are the + * values of those parameters. + * + *

    The document may have missing parameters. Developers are advised to implement defensive + * handling measures. + */ + @NonNull private final GenericDocument mParameters; + + private ExecuteAppFunctionRequest( + @NonNull String targetPackageName, + @NonNull String functionIdentifier, + @NonNull Bundle extras, + @NonNull GenericDocument parameters) { + mTargetPackageName = Objects.requireNonNull(targetPackageName); + mFunctionIdentifier = Objects.requireNonNull(functionIdentifier); + mExtras = Objects.requireNonNull(extras); + mParameters = Objects.requireNonNull(parameters); + } + + /** Returns the package name of the app that hosts the function. */ + @NonNull + public String getTargetPackageName() { + return mTargetPackageName; + } + + /** + * Returns the unique string identifier of the app function to be executed. + * + *

    When there is a package change or the device starts up, the metadata of available + * functions is indexed by AppSearch. AppSearch stores the indexed information as {@code + * AppFunctionStaticMetadata} document. + * + *

    The ID can be obtained by querying the {@code AppFunctionStaticMetadata} documents from + * AppSearch. + * + *

    If the {@code functionId} provided is invalid, the caller will get an invalid argument + * response. + */ + @NonNull + public String getFunctionIdentifier() { + return mFunctionIdentifier; + } + + /** + * Returns the function parameters. The key is the parameter name, and the value is the + * parameter value. + * + *

    The {@link GenericDocument} may have missing parameters. Developers are advised to + * implement defensive handling measures. + * + *

    Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be + * obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This + * metadata will contain enough information for the caller to resolve the required parameters + * either using information from the metadata itself or using the AppFunction SDK for function + * callers. + */ + @NonNull + public GenericDocument getParameters() { + return mParameters; + } + + /** Returns the additional data relevant to this function execution. */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + + /** Builder for {@link ExecuteAppFunctionRequest}. */ + public static final class Builder { + @NonNull private final String mTargetPackageName; + @NonNull private final String mFunctionIdentifier; + @NonNull private Bundle mExtras = Bundle.EMPTY; + + @NonNull + private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build(); + + public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) { + mTargetPackageName = Objects.requireNonNull(targetPackageName); + mFunctionIdentifier = Objects.requireNonNull(functionIdentifier); + } + + /** Sets the additional data relevant to this function execution. */ + @NonNull + public Builder setExtras(@NonNull Bundle extras) { + mExtras = Objects.requireNonNull(extras); + return this; + } + + /** Sets the function parameters. */ + @NonNull + public Builder setParameters(@NonNull GenericDocument parameters) { + Objects.requireNonNull(parameters); + mParameters = parameters; + return this; + } + + /** Builds the {@link ExecuteAppFunctionRequest}. */ + @NonNull + public ExecuteAppFunctionRequest build() { + return new ExecuteAppFunctionRequest( + mTargetPackageName, mFunctionIdentifier, mExtras, mParameters); + } + } +} diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java new file mode 100644 index 000000000000..7c5ddcd9edfb --- /dev/null +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java @@ -0,0 +1,359 @@ +/* + * 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.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. + * + *

    This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel + * functionality and exposes it here as a sidecar library (avoiding direct dependency on the + * platform API). + */ +public final class ExecuteAppFunctionResponse { + /** + * The name of the property that stores the function return value within the {@code + * resultDocument}. + * + *

    See {@link GenericDocument#getProperty(String)} for more information. + * + *

    If the function returns {@code void} or throws an error, the {@code resultDocument} will + * be empty {@link GenericDocument}. + * + *

    If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will + * return {@code null}. + * + *

    See {@link #getResultDocument} for more information on extracting the return value. + */ + public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; + + /** + * The call was successful. + * + *

    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. + * + *

    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. + * + *

    This error may be considered similar to {@link IllegalArgumentException}. + * + *

    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. + * + *

    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. + * + *

    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. + * + *

    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. + * + *

    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. + * + *

    This error is thrown when the service is connected in the remote application but an + * unexpected error is thrown from the bound application. + * + *

    This error is in the {@link #ERROR_CATEGORY_APP} category. + */ + public static final int RESULT_APP_UNKNOWN_ERROR = 3000; + + /** + * The error category is unknown. + * + *

    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. + * + *

    For example, the caller provided invalid parameters in the execution request e.g. an + * invalid function ID. + * + *

    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. + * + *

    For example, the AppFunctionService implementation is not found by the system. + * + *

    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. + * + *

    For example, the app crashed when the system is executing the request. + * + *

    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. + * + *

    The return value is stored in a {@link GenericDocument} with the key {@link + * #PROPERTY_RETURN_VALUE}. + * + *

    See {@link #getResultDocument} for more information on extracting the return value. + */ + @NonNull private final GenericDocument mResultDocument; + + /** Returns the additional metadata data relevant to this function execution response. */ + @NonNull private final Bundle mExtras; + + private ExecuteAppFunctionResponse( + @NonNull GenericDocument resultDocument, + @NonNull Bundle extras, + @ResultCode int resultCode, + @Nullable String errorMessage) { + mResultDocument = Objects.requireNonNull(resultDocument); + mExtras = Objects.requireNonNull(extras); + mResultCode = resultCode; + mErrorMessage = errorMessage; + } + + /** + * Returns result codes from throwable. + * + * @hide + */ + static @ResultCode int getResultCode(@NonNull Throwable t) { + if (t instanceof IllegalArgumentException) { + return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT; + } + return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR; + } + + /** + * Returns a successful response. + * + * @param resultDocument The return value of the executed function. + * @param extras The additional metadata data relevant to this function execution response. + */ + @NonNull + public static ExecuteAppFunctionResponse newSuccess( + @NonNull GenericDocument resultDocument, @Nullable Bundle extras) { + Objects.requireNonNull(resultDocument); + Bundle actualExtras = getActualExtras(extras); + + return new ExecuteAppFunctionResponse( + resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null); + } + + /** + * Returns a failure response. + * + * @param resultCode The result code of the app function execution. + * @param extras The additional metadata data relevant to this function execution response. + * @param errorMessage The error message associated with the result, if any. + */ + @NonNull + public static ExecuteAppFunctionResponse newFailure( + @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) { + if (resultCode == RESULT_OK) { + throw new IllegalArgumentException("resultCode must not be RESULT_OK"); + } + Bundle actualExtras = getActualExtras(extras); + GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build(); + return new ExecuteAppFunctionResponse( + emptyDocument, actualExtras, resultCode, errorMessage); + } + + private static Bundle getActualExtras(@Nullable Bundle extras) { + if (extras == null) { + return Bundle.EMPTY; + } + return extras; + } + + /** + * Returns the error category of the {@link ExecuteAppFunctionResponse}. + * + *

    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. + * + *

    When constructing a {@link #newFailure} response, use the appropriate result code value to + * ensure correct categorization of the failed response. + * + *

    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}. + * + *

    See {@link ErrorCategory} for a complete list of error categories and their corresponding + * result code ranges. + */ + @ErrorCategory + public int getErrorCategory() { + if (mResultCode >= 1000 && mResultCode < 2000) { + return ERROR_CATEGORY_REQUEST_ERROR; + } + if (mResultCode >= 2000 && mResultCode < 3000) { + return ERROR_CATEGORY_SYSTEM; + } + if (mResultCode >= 3000 && mResultCode < 4000) { + return ERROR_CATEGORY_APP; + } + return ERROR_CATEGORY_UNKNOWN; + } + + /** + * Returns a generic document containing the return value of the executed function. + * + *

    The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. + * + *

    An empty document is returned if {@link #isSuccess} is {@code false} or if the executed + * function does not produce a return value. + * + *

    Sample code for extracting the return value: + * + *

    +     *     GenericDocument resultDocument = response.getResultDocument();
    +     *     Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE);
    +     *     if (returnValue != null) {
    +     *       // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString},
    +     *       // {@link GenericDocument#getPropertyLong} etc.
    +     *       // Do something with the returnValue
    +     *     }
    +     * 
    + */ + @NonNull + public GenericDocument getResultDocument() { + return mResultDocument; + } + + /** Returns the extras of the app function execution. */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + + /** + * Returns {@code true} if {@link #getResultCode} equals {@link + * ExecuteAppFunctionResponse#RESULT_OK}. + */ + public boolean isSuccess() { + return getResultCode() == RESULT_OK; + } + + /** + * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}. + */ + @ResultCode + public int getResultCode() { + return mResultCode; + } + + /** + * Returns the error message associated with this result. + * + *

    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 new file mode 100644 index 000000000000..56f2725fccc7 --- /dev/null +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.extensions.appfunctions; + +import android.annotation.NonNull; + +/** + * Utility class containing methods to convert Sidecar objects of AppFunctions API into the + * underlying platform classes. + * + * @hide + */ +public final class SidecarConverter { + private SidecarConverter() {} + + /** + * Converts sidecar's {@link ExecuteAppFunctionRequest} into platform's {@link + * android.app.appfunctions.ExecuteAppFunctionRequest} + * + * @hide + */ + @NonNull + public static android.app.appfunctions.ExecuteAppFunctionRequest + getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) { + return new android.app.appfunctions.ExecuteAppFunctionRequest.Builder( + request.getTargetPackageName(), request.getFunctionIdentifier()) + .setExtras(request.getExtras()) + .setParameters(request.getParameters()) + .build(); + } + + /** + * Converts sidecar's {@link ExecuteAppFunctionResponse} into platform's {@link + * android.app.appfunctions.ExecuteAppFunctionResponse} + * + * @hide + */ + @NonNull + public static android.app.appfunctions.ExecuteAppFunctionResponse + getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) { + if (response.isSuccess()) { + return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess( + response.getResultDocument(), response.getExtras()); + } else { + return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure( + response.getResultCode(), response.getErrorMessage(), response.getExtras()); + } + } + + /** + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} into sidecar's + * {@link ExecuteAppFunctionRequest} + * + * @hide + */ + @NonNull + public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest( + @NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) { + return new ExecuteAppFunctionRequest.Builder( + request.getTargetPackageName(), request.getFunctionIdentifier()) + .setExtras(request.getExtras()) + .setParameters(request.getParameters()) + .build(); + } + + /** + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} into + * sidecar's {@link ExecuteAppFunctionResponse} + * + * @hide + */ + @NonNull + public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse( + @NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) { + if (response.isSuccess()) { + return ExecuteAppFunctionResponse.newSuccess( + response.getResultDocument(), response.getExtras()); + } else { + return ExecuteAppFunctionResponse.newFailure( + response.getResultCode(), response.getErrorMessage(), response.getExtras()); + } + } +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java deleted file mode 100644 index 2075104ff868..000000000000 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.appfunctions.sidecar; - -import android.Manifest; -import android.annotation.CallbackExecutor; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.annotation.UserHandleAware; -import android.content.Context; -import android.os.CancellationSignal; -import android.os.OutcomeReceiver; - -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. - * - *

    App function is a specific piece of functionality that an app offers to the system. These - * functionalities can be integrated into various system features. - * - *

    This class wraps {@link android.app.appfunctions.AppFunctionManager} functionalities and - * exposes it here as a sidecar library (avoiding direct dependency on the platform API). - */ -public final class AppFunctionManager { - /** - * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset - * enabled state to the default value. - */ - public static final int APP_FUNCTION_STATE_DEFAULT = 0; - - /** - * The app function is enabled. To enable an app function, call {@link #setAppFunctionEnabled} - * with this value. - */ - public static final int APP_FUNCTION_STATE_ENABLED = 1; - - /** - * The app function is disabled. To disable an app function, call {@link #setAppFunctionEnabled} - * with this value. - */ - public static final int APP_FUNCTION_STATE_DISABLED = 2; - - /** - * The enabled state of the app function. - * - * @hide - */ - @IntDef( - prefix = {"APP_FUNCTION_STATE_"}, - value = { - APP_FUNCTION_STATE_DEFAULT, - APP_FUNCTION_STATE_ENABLED, - APP_FUNCTION_STATE_DISABLED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface EnabledState {} - - private final android.app.appfunctions.AppFunctionManager mManager; - private final Context mContext; - - /** - * Creates an instance. - * - * @param context A {@link Context}. - * @throws java.lang.IllegalStateException if the underlying {@link - * android.app.appfunctions.AppFunctionManager} is not found. - */ - public AppFunctionManager(Context context) { - mContext = Objects.requireNonNull(context); - mManager = context.getSystemService(android.app.appfunctions.AppFunctionManager.class); - if (mManager == null) { - throw new IllegalStateException( - "Underlying AppFunctionManager system service not found."); - } - } - - /** - * Executes the app function. - * - *

    Proxies request and response to the underlying {@link - * android.app.appfunctions.AppFunctionManager#executeAppFunction}, converting the request and - * response in the appropriate type required by the function. - * - *

    See {@link android.app.appfunctions.AppFunctionManager#executeAppFunction} for the - * documented behaviour of this method. - */ - @RequiresPermission( - anyOf = { - Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, - Manifest.permission.EXECUTE_APP_FUNCTIONS - }, - conditional = true) - public void executeAppFunction( - @NonNull ExecuteAppFunctionRequest sidecarRequest, - @NonNull @CallbackExecutor Executor executor, - @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer callback) { - Objects.requireNonNull(sidecarRequest); - Objects.requireNonNull(executor); - Objects.requireNonNull(callback); - - android.app.appfunctions.ExecuteAppFunctionRequest platformRequest = - SidecarConverter.getPlatformExecuteAppFunctionRequest(sidecarRequest); - mManager.executeAppFunction( - platformRequest, - executor, - cancellationSignal, - (platformResponse) -> { - callback.accept( - SidecarConverter.getSidecarExecuteAppFunctionResponse( - platformResponse)); - }); - } - - /** - * Returns a boolean through a callback, indicating whether the app function is enabled. - * - *

    See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the - * documented behaviour of this method. - */ - @RequiresPermission( - anyOf = { - Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, - Manifest.permission.EXECUTE_APP_FUNCTIONS - }, - conditional = true) - public void isAppFunctionEnabled( - @NonNull String functionIdentifier, - @NonNull String targetPackage, - @NonNull Executor executor, - @NonNull OutcomeReceiver callback) { - mManager.isAppFunctionEnabled(functionIdentifier, targetPackage, executor, callback); - } - - /** - * Returns a boolean through a callback, indicating whether the app function is enabled. - * - *

    See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the - * documented behaviour of this method. - */ - public void isAppFunctionEnabled( - @NonNull String functionIdentifier, - @NonNull Executor executor, - @NonNull OutcomeReceiver callback) { - mManager.isAppFunctionEnabled(functionIdentifier, executor, callback); - } - - /** - * Sets the enabled state of the app function owned by the calling package. - * - *

    See {@link android.app.appfunctions.AppFunctionManager#setAppFunctionEnabled} for the - * documented behavoir of this method. - */ - // Constants in @EnabledState should always mirror those in - // android.app.appfunctions.AppFunctionManager. - @SuppressLint("WrongConstant") - @UserHandleAware - public void setAppFunctionEnabled( - @NonNull String functionIdentifier, - @EnabledState int newEnabledState, - @NonNull Executor executor, - @NonNull OutcomeReceiver callback) { - mManager.setAppFunctionEnabled(functionIdentifier, newEnabledState, executor, callback); - } -} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java deleted file mode 100644 index 0dc87e45b7e3..000000000000 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.appfunctions.sidecar; - -import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE; - -import android.annotation.MainThread; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.Service; -import android.content.Intent; -import android.os.Binder; -import android.os.CancellationSignal; -import android.os.IBinder; -import android.util.Log; - -import java.util.function.Consumer; - -/** - * Abstract base class to provide app functions to the system. - * - *

    Include the following in the manifest: - * - *

    - * {@literal
    - * 
    - *    
    - *      
    - *    
    - * 
    - * }
    - * 
    - * - *

    This class wraps {@link android.app.appfunctions.AppFunctionService} functionalities and - * exposes it here as a sidecar library (avoiding direct dependency on the platform API). - * - * @see AppFunctionManager - */ -public abstract class AppFunctionService extends Service { - /** - * The permission to only allow system access to the functions through {@link - * AppFunctionManagerService}. - */ - @NonNull - public static final String BIND_APP_FUNCTION_SERVICE = - "android.permission.BIND_APP_FUNCTION_SERVICE"; - - /** - * The {@link Intent} that must be declared as handled by the service. To be supported, the - * service must also require the {@link BIND_APP_FUNCTION_SERVICE} permission so that other - * applications can not abuse it. - */ - @NonNull - public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; - - private final Binder mBinder = - android.app.appfunctions.AppFunctionService.createBinder( - /* context= */ this, - /* onExecuteFunction= */ (platformRequest, - callingPackage, - cancellationSignal, - callback) -> { - AppFunctionService.this.onExecuteFunction( - SidecarConverter.getSidecarExecuteAppFunctionRequest( - platformRequest), - callingPackage, - cancellationSignal, - (sidecarResponse) -> { - callback.accept( - SidecarConverter.getPlatformExecuteAppFunctionResponse( - sidecarResponse)); - }); - }); - - @NonNull - @Override - public final IBinder onBind(@Nullable Intent intent) { - return mBinder; - } - - /** - * Called by the system to execute a specific app function. - * - *

    This method is triggered when the system requests your AppFunctionService to handle a - * particular function you have registered and made available. - * - *

    To ensure proper routing of function requests, assign a unique identifier to each - * function. This identifier doesn't need to be globally unique, but it must be unique within - * your app. For example, a function to order food could be identified as "orderFood". In most - * cases this identifier should come from the ID automatically generated by the AppFunctions - * SDK. You can determine the specific function to invoke by calling {@link - * ExecuteAppFunctionRequest#getFunctionIdentifier()}. - * - *

    This method is always triggered in the main thread. You should run heavy tasks on a worker - * thread and dispatch the result with the given callback. You should always report back the - * result using the callback, no matter if the execution was successful or not. - * - *

    This method also accepts a {@link CancellationSignal} that the app should listen to cancel - * the execution of function if requested by the system. - * - * @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. - */ - @MainThread - public abstract void onExecuteFunction( - @NonNull ExecuteAppFunctionRequest request, - @NonNull String callingPackage, - @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer callback); -} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java deleted file mode 100644 index 9cf4c6a9b3c5..000000000000 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.appfunctions.sidecar; - -import android.annotation.NonNull; -import android.app.appsearch.GenericDocument; -import android.os.Bundle; - -import java.util.Objects; - -/** - * A request to execute an app function. - * - *

    This class copies {@link android.app.appfunctions.ExecuteAppFunctionRequest} without parcel - * functionality and exposes it here as a sidecar library (avoiding direct dependency on the - * platform API). - */ -public final class ExecuteAppFunctionRequest { - /** Returns the package name of the app that hosts the function. */ - @NonNull private final String mTargetPackageName; - - /** - * The unique string identifier of the app function to be executed. This identifier is used to - * execute a specific app function. - */ - @NonNull private final String mFunctionIdentifier; - - /** Returns additional metadata relevant to this function execution request. */ - @NonNull private final Bundle mExtras; - - /** - * Returns the parameters required to invoke this function. Within this [GenericDocument], the - * property names are the names of the function parameters and the property values are the - * values of those parameters. - * - *

    The document may have missing parameters. Developers are advised to implement defensive - * handling measures. - */ - @NonNull private final GenericDocument mParameters; - - private ExecuteAppFunctionRequest( - @NonNull String targetPackageName, - @NonNull String functionIdentifier, - @NonNull Bundle extras, - @NonNull GenericDocument parameters) { - mTargetPackageName = Objects.requireNonNull(targetPackageName); - mFunctionIdentifier = Objects.requireNonNull(functionIdentifier); - mExtras = Objects.requireNonNull(extras); - mParameters = Objects.requireNonNull(parameters); - } - - /** Returns the package name of the app that hosts the function. */ - @NonNull - public String getTargetPackageName() { - return mTargetPackageName; - } - - /** - * Returns the unique string identifier of the app function to be executed. - * - *

    When there is a package change or the device starts up, the metadata of available - * functions is indexed by AppSearch. AppSearch stores the indexed information as {@code - * AppFunctionStaticMetadata} document. - * - *

    The ID can be obtained by querying the {@code AppFunctionStaticMetadata} documents from - * AppSearch. - * - *

    If the {@code functionId} provided is invalid, the caller will get an invalid argument - * response. - */ - @NonNull - public String getFunctionIdentifier() { - return mFunctionIdentifier; - } - - /** - * Returns the function parameters. The key is the parameter name, and the value is the - * parameter value. - * - *

    The {@link GenericDocument} may have missing parameters. Developers are advised to - * implement defensive handling measures. - * - *

    Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be - * obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This - * metadata will contain enough information for the caller to resolve the required parameters - * either using information from the metadata itself or using the AppFunction SDK for function - * callers. - */ - @NonNull - public GenericDocument getParameters() { - return mParameters; - } - - /** Returns the additional data relevant to this function execution. */ - @NonNull - public Bundle getExtras() { - return mExtras; - } - - /** Builder for {@link ExecuteAppFunctionRequest}. */ - public static final class Builder { - @NonNull private final String mTargetPackageName; - @NonNull private final String mFunctionIdentifier; - @NonNull private Bundle mExtras = Bundle.EMPTY; - - @NonNull - private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build(); - - public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) { - mTargetPackageName = Objects.requireNonNull(targetPackageName); - mFunctionIdentifier = Objects.requireNonNull(functionIdentifier); - } - - /** Sets the additional data relevant to this function execution. */ - @NonNull - public Builder setExtras(@NonNull Bundle extras) { - mExtras = Objects.requireNonNull(extras); - return this; - } - - /** Sets the function parameters. */ - @NonNull - public Builder setParameters(@NonNull GenericDocument parameters) { - Objects.requireNonNull(parameters); - mParameters = parameters; - return this; - } - - /** Builds the {@link ExecuteAppFunctionRequest}. */ - @NonNull - public ExecuteAppFunctionRequest build() { - return new ExecuteAppFunctionRequest( - mTargetPackageName, mFunctionIdentifier, mExtras, mParameters); - } - } -} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java deleted file mode 100644 index c806a0780551..000000000000 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.appfunctions.sidecar; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.appsearch.GenericDocument; -import android.os.Bundle; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -/** - * The response to an app function execution. - * - *

    This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel - * functionality and exposes it here as a sidecar library (avoiding direct dependency on the - * platform API). - */ -public final class ExecuteAppFunctionResponse { - /** - * The name of the property that stores the function return value within the {@code - * resultDocument}. - * - *

    See {@link GenericDocument#getProperty(String)} for more information. - * - *

    If the function returns {@code void} or throws an error, the {@code resultDocument} will - * be empty {@link GenericDocument}. - * - *

    If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will - * return {@code null}. - * - *

    See {@link #getResultDocument} for more information on extracting the return value. - */ - public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; - - /** - * The call was successful. - * - *

    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. - * - *

    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. - * - *

    This error may be considered similar to {@link IllegalArgumentException}. - * - *

    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. - * - *

    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. - * - *

    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. - * - *

    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. - * - *

    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. - * - *

    This error is thrown when the service is connected in the remote application but an - * unexpected error is thrown from the bound application. - * - *

    This error is in the {@link #ERROR_CATEGORY_APP} category. - */ - public static final int RESULT_APP_UNKNOWN_ERROR = 3000; - - /** - * The error category is unknown. - * - *

    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. - * - *

    For example, the caller provided invalid parameters in the execution request e.g. an - * invalid function ID. - * - *

    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. - * - *

    For example, the AppFunctionService implementation is not found by the system. - * - *

    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. - * - *

    For example, the app crashed when the system is executing the request. - * - *

    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. - * - *

    The return value is stored in a {@link GenericDocument} with the key {@link - * #PROPERTY_RETURN_VALUE}. - * - *

    See {@link #getResultDocument} for more information on extracting the return value. - */ - @NonNull private final GenericDocument mResultDocument; - - /** Returns the additional metadata data relevant to this function execution response. */ - @NonNull private final Bundle mExtras; - - private ExecuteAppFunctionResponse( - @NonNull GenericDocument resultDocument, - @NonNull Bundle extras, - @ResultCode int resultCode, - @Nullable String errorMessage) { - mResultDocument = Objects.requireNonNull(resultDocument); - mExtras = Objects.requireNonNull(extras); - mResultCode = resultCode; - mErrorMessage = errorMessage; - } - - /** - * Returns result codes from throwable. - * - * @hide - */ - static @ResultCode int getResultCode(@NonNull Throwable t) { - if (t instanceof IllegalArgumentException) { - return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT; - } - return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR; - } - - /** - * Returns a successful response. - * - * @param resultDocument The return value of the executed function. - * @param extras The additional metadata data relevant to this function execution response. - */ - @NonNull - public static ExecuteAppFunctionResponse newSuccess( - @NonNull GenericDocument resultDocument, @Nullable Bundle extras) { - Objects.requireNonNull(resultDocument); - Bundle actualExtras = getActualExtras(extras); - - return new ExecuteAppFunctionResponse( - resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null); - } - - /** - * Returns a failure response. - * - * @param resultCode The result code of the app function execution. - * @param extras The additional metadata data relevant to this function execution response. - * @param errorMessage The error message associated with the result, if any. - */ - @NonNull - public static ExecuteAppFunctionResponse newFailure( - @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) { - if (resultCode == RESULT_OK) { - throw new IllegalArgumentException("resultCode must not be RESULT_OK"); - } - Bundle actualExtras = getActualExtras(extras); - GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build(); - return new ExecuteAppFunctionResponse( - emptyDocument, actualExtras, resultCode, errorMessage); - } - - private static Bundle getActualExtras(@Nullable Bundle extras) { - if (extras == null) { - return Bundle.EMPTY; - } - return extras; - } - - /** - * Returns the error category of the {@link ExecuteAppFunctionResponse}. - * - *

    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. - * - *

    When constructing a {@link #newFailure} response, use the appropriate result code value to - * ensure correct categorization of the failed response. - * - *

    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}. - * - *

    See {@link ErrorCategory} for a complete list of error categories and their corresponding - * result code ranges. - */ - @ErrorCategory - public int getErrorCategory() { - if (mResultCode >= 1000 && mResultCode < 2000) { - return ERROR_CATEGORY_REQUEST_ERROR; - } - if (mResultCode >= 2000 && mResultCode < 3000) { - return ERROR_CATEGORY_SYSTEM; - } - if (mResultCode >= 3000 && mResultCode < 4000) { - return ERROR_CATEGORY_APP; - } - return ERROR_CATEGORY_UNKNOWN; - } - - /** - * Returns a generic document containing the return value of the executed function. - * - *

    The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. - * - *

    An empty document is returned if {@link #isSuccess} is {@code false} or if the executed - * function does not produce a return value. - * - *

    Sample code for extracting the return value: - * - *

    -     *     GenericDocument resultDocument = response.getResultDocument();
    -     *     Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE);
    -     *     if (returnValue != null) {
    -     *       // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString},
    -     *       // {@link GenericDocument#getPropertyLong} etc.
    -     *       // Do something with the returnValue
    -     *     }
    -     * 
    - */ - @NonNull - public GenericDocument getResultDocument() { - return mResultDocument; - } - - /** Returns the extras of the app function execution. */ - @NonNull - public Bundle getExtras() { - return mExtras; - } - - /** - * Returns {@code true} if {@link #getResultCode} equals {@link - * ExecuteAppFunctionResponse#RESULT_OK}. - */ - public boolean isSuccess() { - return getResultCode() == RESULT_OK; - } - - /** - * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}. - */ - @ResultCode - public int getResultCode() { - return mResultCode; - } - - /** - * Returns the error message associated with this result. - * - *

    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/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java deleted file mode 100644 index b1b05f79f33f..000000000000 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.appfunctions.sidecar; - -import android.annotation.NonNull; - -/** - * Utility class containing methods to convert Sidecar objects of AppFunctions API into the - * underlying platform classes. - * - * @hide - */ -public final class SidecarConverter { - private SidecarConverter() {} - - /** - * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} - * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} - * - * @hide - */ - @NonNull - public static android.app.appfunctions.ExecuteAppFunctionRequest - getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) { - return new - android.app.appfunctions.ExecuteAppFunctionRequest.Builder( - request.getTargetPackageName(), - request.getFunctionIdentifier()) - .setExtras(request.getExtras()) - .setParameters(request.getParameters()) - .build(); - } - - /** - * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} - * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} - * - * @hide - */ - @NonNull - public static android.app.appfunctions.ExecuteAppFunctionResponse - getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) { - if (response.isSuccess()) { - return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess( - response.getResultDocument(), response.getExtras()); - } else { - return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure( - response.getResultCode(), - response.getErrorMessage(), - response.getExtras()); - } - } - - /** - * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} - * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} - * - * @hide - */ - @NonNull - public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest( - @NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) { - return new ExecuteAppFunctionRequest.Builder( - request.getTargetPackageName(), - request.getFunctionIdentifier()) - .setExtras(request.getExtras()) - .setParameters(request.getParameters()) - .build(); - } - - /** - * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} - * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} - * - * @hide - */ - @NonNull - public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse( - @NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) { - if (response.isSuccess()) { - return ExecuteAppFunctionResponse.newSuccess( - response.getResultDocument(), response.getExtras()); - } else { - return ExecuteAppFunctionResponse.newFailure( - response.getResultCode(), - response.getErrorMessage(), - response.getExtras()); - } - } -} diff --git a/libs/appfunctions/tests/Android.bp b/libs/appfunctions/tests/Android.bp index 6f5eff305d8d..db79675ae9f7 100644 --- a/libs/appfunctions/tests/Android.bp +++ b/libs/appfunctions/tests/Android.bp @@ -25,7 +25,7 @@ android_test { "androidx.test.rules", "androidx.test.ext.junit", "androidx.core_core-ktx", - "com.google.android.appfunctions.sidecar.impl", + "com.android.extensions.appfunctions.impl", "junit", "kotlin-test", "mockito-target-extended-minus-junit4", diff --git a/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt new file mode 100644 index 000000000000..6118e6ce4ab2 --- /dev/null +++ b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.extensions.appfunctions.tests + +import android.app.appfunctions.ExecuteAppFunctionRequest +import android.app.appfunctions.ExecuteAppFunctionResponse +import android.app.appsearch.GenericDocument +import android.os.Bundle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.extensions.appfunctions.SidecarConverter +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SidecarConverterTest { + @Test + fun getSidecarExecuteAppFunctionRequest_sameContents() { + val extras = Bundle() + extras.putString("extra", "value") + val parameters: GenericDocument = + GenericDocument.Builder>("", "", "") + .setPropertyLong("testLong", 23) + .build() + val platformRequest: ExecuteAppFunctionRequest = + ExecuteAppFunctionRequest.Builder("targetPkg", "targetFunctionId") + .setExtras(extras) + .setParameters(parameters) + .build() + + val sidecarRequest = SidecarConverter.getSidecarExecuteAppFunctionRequest(platformRequest) + + assertThat(sidecarRequest.targetPackageName).isEqualTo("targetPkg") + assertThat(sidecarRequest.functionIdentifier).isEqualTo("targetFunctionId") + assertThat(sidecarRequest.parameters).isEqualTo(parameters) + assertThat(sidecarRequest.extras.size()).isEqualTo(1) + assertThat(sidecarRequest.extras.getString("extra")).isEqualTo("value") + } + + @Test + fun getPlatformExecuteAppFunctionRequest_sameContents() { + val extras = Bundle() + extras.putString("extra", "value") + val parameters: GenericDocument = + GenericDocument.Builder>("", "", "") + .setPropertyLong("testLong", 23) + .build() + val sidecarRequest = + com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder( + "targetPkg", + "targetFunctionId" + ) + .setExtras(extras) + .setParameters(parameters) + .build() + + val platformRequest = SidecarConverter.getPlatformExecuteAppFunctionRequest(sidecarRequest) + + assertThat(platformRequest.targetPackageName).isEqualTo("targetPkg") + assertThat(platformRequest.functionIdentifier).isEqualTo("targetFunctionId") + assertThat(platformRequest.parameters).isEqualTo(parameters) + assertThat(platformRequest.extras.size()).isEqualTo(1) + assertThat(platformRequest.extras.getString("extra")).isEqualTo("value") + } + + @Test + fun getSidecarExecuteAppFunctionResponse_successResponse_sameContents() { + val resultGd: GenericDocument = + GenericDocument.Builder>("", "", "") + .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true) + .build() + val platformResponse = ExecuteAppFunctionResponse.newSuccess(resultGd, null) + + 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>("", "", "").build() + val platformResponse = + ExecuteAppFunctionResponse.newFailure( + ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, + null, + null + ) + + val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse( + platformResponse + ) + + 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() + } + + @Test + fun getPlatformExecuteAppFunctionResponse_successResponse_sameContents() { + val resultGd: GenericDocument = + GenericDocument.Builder>("", "", "") + .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true) + .build() + val sidecarResponse = + com.android.extensions.appfunctions.ExecuteAppFunctionResponse.newSuccess( + resultGd, + null + ) + + 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>("", "", "").build() + val sidecarResponse = + com.android.extensions.appfunctions.ExecuteAppFunctionResponse.newFailure( + ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, + null, + null + ) + + val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse( + sidecarResponse + ) + + 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() + } +} diff --git a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt deleted file mode 100644 index 264f84209caf..000000000000 --- a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.appfunctions.sidecar.tests - -import android.app.appfunctions.ExecuteAppFunctionRequest -import android.app.appfunctions.ExecuteAppFunctionResponse -import android.app.appsearch.GenericDocument -import android.os.Bundle -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.android.appfunctions.sidecar.SidecarConverter -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class SidecarConverterTest { - @Test - fun getSidecarExecuteAppFunctionRequest_sameContents() { - val extras = Bundle() - extras.putString("extra", "value") - val parameters: GenericDocument = - GenericDocument.Builder>("", "", "") - .setPropertyLong("testLong", 23) - .build() - val platformRequest: ExecuteAppFunctionRequest = - ExecuteAppFunctionRequest.Builder("targetPkg", "targetFunctionId") - .setExtras(extras) - .setParameters(parameters) - .build() - - val sidecarRequest = SidecarConverter.getSidecarExecuteAppFunctionRequest(platformRequest) - - assertThat(sidecarRequest.targetPackageName).isEqualTo("targetPkg") - assertThat(sidecarRequest.functionIdentifier).isEqualTo("targetFunctionId") - assertThat(sidecarRequest.parameters).isEqualTo(parameters) - assertThat(sidecarRequest.extras.size()).isEqualTo(1) - assertThat(sidecarRequest.extras.getString("extra")).isEqualTo("value") - } - - @Test - fun getPlatformExecuteAppFunctionRequest_sameContents() { - val extras = Bundle() - extras.putString("extra", "value") - val parameters: GenericDocument = - GenericDocument.Builder>("", "", "") - .setPropertyLong("testLong", 23) - .build() - val sidecarRequest = - com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder( - "targetPkg", - "targetFunctionId" - ) - .setExtras(extras) - .setParameters(parameters) - .build() - - val platformRequest = SidecarConverter.getPlatformExecuteAppFunctionRequest(sidecarRequest) - - assertThat(platformRequest.targetPackageName).isEqualTo("targetPkg") - assertThat(platformRequest.functionIdentifier).isEqualTo("targetFunctionId") - assertThat(platformRequest.parameters).isEqualTo(parameters) - assertThat(platformRequest.extras.size()).isEqualTo(1) - assertThat(platformRequest.extras.getString("extra")).isEqualTo("value") - } - - @Test - fun getSidecarExecuteAppFunctionResponse_successResponse_sameContents() { - val resultGd: GenericDocument = - GenericDocument.Builder>("", "", "") - .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true) - .build() - val platformResponse = ExecuteAppFunctionResponse.newSuccess(resultGd, null) - - 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>("", "", "").build() - val platformResponse = - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, - null, - null - ) - - val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse( - platformResponse - ) - - 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() - } - - @Test - fun getPlatformExecuteAppFunctionResponse_successResponse_sameContents() { - val resultGd: GenericDocument = - GenericDocument.Builder>("", "", "") - .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true) - .build() - val sidecarResponse = com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse - .newSuccess(resultGd, null) - - 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>("", "", "").build() - val sidecarResponse = - com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, - null, - null - ) - - val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse( - sidecarResponse - ) - - 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() - } -} -- cgit v1.2.3-59-g8ed1b From fdd0e6bbee3fd65df17c0ac8486c24015f9cb072 Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Thu, 7 Nov 2024 23:07:21 +0000 Subject: Update executeAppFunction to take a OutcomeReceiver Flag: android.app.appfunctions.flags.enable_app_function_manager Test: Existing CTS Bug: 376426049 Change-Id: Icec71cdcb79d3f22854cd4c28b65ca6d56d48883 --- core/api/current.txt | 47 ++-- .../app/appfunctions/AppFunctionException.aidl | 21 ++ .../app/appfunctions/AppFunctionException.java | 260 +++++++++++++++++++ .../app/appfunctions/AppFunctionManager.java | 38 +-- .../appfunctions/AppFunctionRuntimeMetadata.java | 4 +- .../app/appfunctions/AppFunctionService.java | 44 +++- .../AppFunctionStaticMetadataHelper.java | 2 +- .../appfunctions/ExecuteAppFunctionResponse.java | 277 +------------------- .../app/appfunctions/GenericDocumentWrapper.java | 11 +- .../appfunctions/IExecuteAppFunctionCallback.aidl | 4 +- .../SafeOneTimeExecuteAppFunctionCallback.java | 42 ++- libs/appfunctions/api/current.txt | 44 ++-- .../appfunctions/AppFunctionException.java | 211 +++++++++++++++ .../appfunctions/AppFunctionManager.java | 23 +- .../appfunctions/AppFunctionService.java | 28 +- .../appfunctions/ExecuteAppFunctionResponse.java | 282 +-------------------- .../extensions/appfunctions/SidecarConverter.java | 43 +++- .../appfunctions/tests/SidecarConverterTest.kt | 73 +++--- .../AppFunctionManagerServiceImpl.java | 64 ++--- .../RunAppFunctionServiceCallback.java | 26 +- 20 files changed, 788 insertions(+), 756 deletions(-) create mode 100644 core/java/android/app/appfunctions/AppFunctionException.aidl create mode 100644 core/java/android/app/appfunctions/AppFunctionException.java create mode 100644 libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java (limited to 'libs/appfunctions/java') diff --git a/core/api/current.txt b/core/api/current.txt index 664b3dd125ef..00196ee376e5 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8790,8 +8790,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 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); + 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); 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); method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); @@ -8803,7 +8826,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); + method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver); field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } @@ -8825,30 +8848,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 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 } } 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. + * + *

    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. + * + *

    This error may be considered similar to {@link IllegalArgumentException}. + * + *

    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. + * + *

    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. + * + *

    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. + * + *

    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. + * + *

    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. + * + *

    This error is thrown when the service is connected in the remote application but an + * unexpected error is thrown from the bound application. + * + *

    This error is in the {@link #ERROR_CATEGORY_APP} category. + */ + public static final int ERROR_APP_UNKNOWN_ERROR = 3000; + + /** + * The error category is unknown. + * + *

    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. + * + *

    For example, the caller provided invalid parameters in the execution request e.g. an + * invalid function ID. + * + *

    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. + * + *

    For example, the AppFunctionService implementation is not found by the system. + * + *

    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. + * + *

    For example, the app crashed when the system is executing the request. + * + *

    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. + * + *

    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. + * + *

    This method returns {@code ERROR_CATEGORY_UNKNOWN} if the error code does not belong to + * any error category. + * + *

    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 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. *

    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}. *

    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} *

    If the function requested for execution is disabled, then the execution result will - * contain {@code ExecuteAppFunctionResponse.RESULT_DISABLED} + * contain {@code AppFunctionException.ERROR_DISABLED} *

    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 callback) { + @NonNull + OutcomeReceiver + 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 callback); + @NonNull + OutcomeReceiver + 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 callback); + @NonNull + OutcomeReceiver + 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 @@ -73,112 +67,6 @@ public final class ExecuteAppFunctionResponse implements Parcelable { */ public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; - /** - * The call was successful. - * - *

    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. - * - *

    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. - * - *

    This error may be considered similar to {@link IllegalArgumentException}. - * - *

    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. - * - *

    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. - * - *

    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. - * - *

    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. - * - *

    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. - * - *

    This error is thrown when the service is connected in the remote application but an - * unexpected error is thrown from the bound application. - * - *

    This error is in the {@link #ERROR_CATEGORY_APP} category. - */ - public static final int RESULT_APP_UNKNOWN_ERROR = 3000; - - /** - * The error category is unknown. - * - *

    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. - * - *

    For example, the caller provided invalid parameters in the execution request e.g. an - * invalid function ID. - * - *

    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. - * - *

    For example, the AppFunctionService implementation is not found by the system. - * - *

    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. - * - *

    For example, the app crashed when the system is executing the request. - * - *

    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. * @@ -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}. - * - *

    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. - * - *

    When constructing a {@link #newFailure} response, use the appropriate result code value to - * ensure correct categorization of the failed response. - * - *

    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}. - * - *

    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 { * *

    The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. * - *

    An empty document is returned if {@link #isSuccess} is {@code false} or if the executed - * function does not produce a return value. - * *

    Sample code for extracting the return value: * *

    @@ -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.
    -     *
    -     * 

    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; *

    {#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. * - *

    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. + *

    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 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 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 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/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); + 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); 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); method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); @@ -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); + method @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver); 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. + * + *

    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. + * + *

    This error may be considered similar to {@link IllegalArgumentException}. + * + *

    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. + * + *

    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. + * + *

    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. + * + *

    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. + * + *

    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. + * + *

    This error is thrown when the service is connected in the remote application but an + * unexpected error is thrown from the bound application. + * + *

    This error is in the {@link #ERROR_CATEGORY_APP} category. + */ + public static final int ERROR_APP_UNKNOWN_ERROR = 3000; + + /** + * The error category is unknown. + * + *

    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. + * + *

    For example, the caller provided invalid parameters in the execution request e.g. an + * invalid function ID. + * + *

    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. + * + *

    For example, the AppFunctionService implementation is not found by the system. + * + *

    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. + * + *

    For example, the app crashed when the system is executing the request. + * + *

    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. + * + *

    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. + * + *

    This method returns {@code ERROR_CATEGORY_UNKNOWN} if the error code does not belong to + * any error category. + * + *

    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 callback) { + @NonNull + OutcomeReceiver + 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 callback); + @NonNull + OutcomeReceiver + 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. - * - *

    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 @@ -50,112 +41,6 @@ public final class ExecuteAppFunctionResponse { */ public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; - /** - * The call was successful. - * - *

    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. - * - *

    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. - * - *

    This error may be considered similar to {@link IllegalArgumentException}. - * - *

    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. - * - *

    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. - * - *

    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. - * - *

    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. - * - *

    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. - * - *

    This error is thrown when the service is connected in the remote application but an - * unexpected error is thrown from the bound application. - * - *

    This error is in the {@link #ERROR_CATEGORY_APP} category. - */ - public static final int RESULT_APP_UNKNOWN_ERROR = 3000; - - /** - * The error category is unknown. - * - *

    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. - * - *

    For example, the caller provided invalid parameters in the execution request e.g. an - * invalid function ID. - * - *

    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. - * - *

    For example, the AppFunctionService implementation is not found by the system. - * - *

    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. - * - *

    For example, the app crashed when the system is executing the request. - * - *

    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. * @@ -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}. - * - *

    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. - * - *

    When constructing a {@link #newFailure} response, use the appropriate result code value to - * ensure correct categorization of the failed response. - * - *

    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}. - * - *

    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 { * *

    The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. * - *

    An empty document is returned if {@link #isSuccess} is {@code false} or if the executed - * function does not produce a return value. - * *

    Sample code for extracting the return value: * *

    @@ -283,77 +87,17 @@ public final class ExecuteAppFunctionResponse {
          *       // Do something with the returnValue
          *     }
          * 
    + * + * @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. - * - *

    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>("", "", "") .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>("", "", "").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>("", "", "").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/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 Date: Fri, 8 Nov 2024 14:56:23 +0000 Subject: AppSearch PropertyPath does not allow non alphanumeric char Flag: android.app.appfunctions.flags.enable_app_function_manager Test: cts Bug: 357551503 Change-Id: I01c3bbb24771b8ad1012259d3972638c1ffdd2c3 --- core/api/current.txt | 2 +- core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java | 3 +-- libs/appfunctions/api/current.txt | 2 +- .../android/extensions/appfunctions/ExecuteAppFunctionResponse.java | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) (limited to 'libs/appfunctions/java') diff --git a/core/api/current.txt b/core/api/current.txt index 843c2eb07297..fd2403d79439 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8857,7 +8857,7 @@ package android.app.appfunctions { method @NonNull public android.app.appsearch.GenericDocument getResultDocument(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; + field public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue"; } } diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java index f7030265b122..acad43b782e5 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java @@ -20,7 +20,6 @@ import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANA import android.annotation.FlaggedApi; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.appsearch.GenericDocument; import android.os.Bundle; import android.os.Parcel; @@ -65,7 +64,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable { * *

    See {@link #getResultDocument} for more information on extracting the return value. */ - public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; + public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue"; /** * Returns the return value of the executed function. diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index f56667df92ca..de402095e195 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -59,7 +59,7 @@ package com.android.extensions.appfunctions { ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle); method @NonNull public android.os.Bundle getExtras(); method @NonNull public android.app.appsearch.GenericDocument getResultDocument(); - field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; + field public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue"; } } diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java index 42c3c033ad38..0826f04a50dd 100644 --- a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java @@ -39,7 +39,7 @@ public final class ExecuteAppFunctionResponse { * *

    See {@link #getResultDocument} for more information on extracting the return value. */ - public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; + public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue"; /** * Returns the return value of the executed function. -- cgit v1.2.3-59-g8ed1b From 56ac636a7daf51639c68c5ecc3b8e3af7ef0c2cc Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Sat, 9 Nov 2024 09:24:36 +0000 Subject: set the error msg to the super constructor Flag: android.app.appfunctions.flags.enable_app_function_manager Test: cts Bug: 357551503 Change-Id: I399d1c8cfc4896fbce7bfe44555070f389bb6946 --- core/java/android/app/appfunctions/AppFunctionException.java | 1 + .../java/com/android/extensions/appfunctions/AppFunctionException.java | 1 + 2 files changed, 2 insertions(+) (limited to 'libs/appfunctions/java') diff --git a/core/java/android/app/appfunctions/AppFunctionException.java b/core/java/android/app/appfunctions/AppFunctionException.java index d33b5055f9cc..cbd1d932ab00 100644 --- a/core/java/android/app/appfunctions/AppFunctionException.java +++ b/core/java/android/app/appfunctions/AppFunctionException.java @@ -141,6 +141,7 @@ public final class AppFunctionException extends Exception implements Parcelable */ public AppFunctionException( @ErrorCode int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) { + super(errorMessage); mErrorCode = errorCode; mErrorMessage = errorMessage; mExtras = Objects.requireNonNull(extras); diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java index 28c3b3df9b1c..2540236f2ce5 100644 --- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java @@ -125,6 +125,7 @@ public final class AppFunctionException extends Exception { public AppFunctionException( int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) { + super(errorMessage); mErrorCode = errorCode; mErrorMessage = errorMessage; mExtras = extras; -- cgit v1.2.3-59-g8ed1b From 6e256ccab44f5e3a6b544adb87650e709ada561f Mon Sep 17 00:00:00 2001 From: Oluwarotimi Adesina Date: Wed, 27 Nov 2024 14:05:57 +0000 Subject: Respect enterprise policy in AppFunctions Flag: android.app.appfunctions.flags.enable_app_function_manager Test: atest CtsAppFunctionTestCases -c Bug: 380442826 Change-Id: I277df0eade4787f906d426cfa5572f441747ad39 --- core/api/current.txt | 1 + .../app/appfunctions/AppFunctionException.java | 14 +++++++-- libs/appfunctions/api/current.txt | 1 + .../appfunctions/AppFunctionException.java | 10 +++++- .../AppFunctionManagerServiceImpl.java | 21 ++++++------- .../server/appfunctions/CallerValidator.java | 8 +++-- .../server/appfunctions/CallerValidatorImpl.java | 36 ++++++++++++++++------ 7 files changed, 63 insertions(+), 28 deletions(-) (limited to 'libs/appfunctions/java') diff --git a/core/api/current.txt b/core/api/current.txt index 0fe8717e8954..f0763733f36b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8878,6 +8878,7 @@ package android.app.appfunctions { field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 field public static final int ERROR_DENIED = 1000; // 0x3e8 field public static final int ERROR_DISABLED = 1002; // 0x3ea + field public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; // 0x7d2 field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9 field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0 diff --git a/core/java/android/app/appfunctions/AppFunctionException.java b/core/java/android/app/appfunctions/AppFunctionException.java index c8d80d3afe43..d8179c7540d9 100644 --- a/core/java/android/app/appfunctions/AppFunctionException.java +++ b/core/java/android/app/appfunctions/AppFunctionException.java @@ -32,8 +32,8 @@ import java.util.Objects; /** * Represents an app function related error. * - *

    This exception may include an {@link AppFunctionException#getExtras() Bundle} - * containing additional error-specific metadata. + *

    This exception may include an {@link AppFunctionException#getExtras() Bundle} containing + * additional error-specific metadata. * *

    The AppFunction SDK can expose structured APIs by packing and unpacking this Bundle. */ @@ -84,6 +84,13 @@ public final class AppFunctionException extends Exception implements Parcelable */ public static final int ERROR_CANCELLED = 2001; + /** + * The operation was disallowed by enterprise policy. + * + *

    This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; + /** * An unknown error occurred while processing the call in the AppFunctionService. * @@ -231,7 +238,8 @@ public final class AppFunctionException extends Exception implements Parcelable ERROR_SYSTEM_ERROR, ERROR_INVALID_ARGUMENT, ERROR_DISABLED, - ERROR_CANCELLED + ERROR_CANCELLED, + ERROR_ENTERPRISE_POLICY_DISALLOWED }) @Retention(RetentionPolicy.SOURCE) public @interface ErrorCode {} diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index de402095e195..139ccfd22b0e 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -16,6 +16,7 @@ package com.android.extensions.appfunctions { field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 field public static final int ERROR_DENIED = 1000; // 0x3e8 field public static final int ERROR_DISABLED = 1002; // 0x3ea + field public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; // 0x7d2 field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9 field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0 diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java index 2540236f2ce5..0c521690b165 100644 --- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java @@ -70,6 +70,13 @@ public final class AppFunctionException extends Exception { */ public static final int ERROR_CANCELLED = 2001; + /** + * The operation was disallowed by enterprise policy. + * + *

    This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; + /** * An unknown error occurred while processing the call in the AppFunctionService. * @@ -189,7 +196,8 @@ public final class AppFunctionException extends Exception { ERROR_SYSTEM_ERROR, ERROR_INVALID_ARGUMENT, ERROR_DISABLED, - ERROR_CANCELLED + ERROR_CANCELLED, + ERROR_ENTERPRISE_POLICY_DISALLOWED }) @Retention(RetentionPolicy.SOURCE) public @interface ErrorCode {} diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index f13e22950e2d..c17c34061d1b 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -24,12 +24,12 @@ import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_E import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.WorkerThread; +import android.app.appfunctions.AppFunctionException; import android.app.appfunctions.AppFunctionManager; import android.app.appfunctions.AppFunctionManagerHelper; import android.app.appfunctions.AppFunctionRuntimeMetadata; import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; -import android.app.appfunctions.AppFunctionException; import android.app.appfunctions.IAppFunctionEnabledCallback; import android.app.appfunctions.IAppFunctionManager; import android.app.appfunctions.IAppFunctionService; @@ -158,8 +158,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } catch (SecurityException exception) { safeExecuteAppFunctionCallback.onError( new AppFunctionException( - AppFunctionException.ERROR_DENIED, - exception.getMessage())); + AppFunctionException.ERROR_DENIED, exception.getMessage())); return null; } @@ -195,12 +194,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { @NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback, @NonNull IBinder callerBinder) { UserHandle targetUser = requestInternal.getUserHandle(); - // TODO(b/354956319): Add and honor the new enterprise policies. - if (mCallerValidator.isUserOrganizationManaged(targetUser)) { + UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid); + if (!mCallerValidator.verifyEnterprisePolicyIsAllowed(callingUser, targetUser)) { safeExecuteAppFunctionCallback.onError( - new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR, - "Cannot run on a device with a device owner or from the managed" - + " profile.")); + new AppFunctionException( + AppFunctionException.ERROR_ENTERPRISE_POLICY_DISALLOWED, + "Cannot run on a user with a restricted enterprise policy")); return; } @@ -442,7 +441,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { if (!bindServiceResult) { Slog.e(TAG, "Failed to bind to the AppFunctionService"); safeExecuteAppFunctionCallback.onError( - new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR, + new AppFunctionException( + AppFunctionException.ERROR_SYSTEM_ERROR, "Failed to bind the AppFunctionService.")); } } @@ -495,8 +495,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { return; } FutureGlobalSearchSession futureGlobalSearchSession = - new FutureGlobalSearchSession( - perUserAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR); + new FutureGlobalSearchSession(perUserAppSearchManager, THREAD_POOL_EXECUTOR); AppFunctionMetadataObserver appFunctionMetadataObserver = new AppFunctionMetadataObserver( user.getUserHandle(), diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java index 5393b939b5ed..61917676e88d 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java +++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java @@ -81,10 +81,12 @@ public interface CallerValidator { @NonNull String functionId); /** - * Checks if the user is organization managed. + * Checks if the app function policy is allowed. * + * @param callingUser The current calling user. * @param targetUser The user which the caller is requesting to execute as. - * @return Whether the user is organization managed. + * @return Whether the app function policy is allowed. */ - boolean isUserOrganizationManaged(@NonNull UserHandle targetUser); + boolean verifyEnterprisePolicyIsAllowed( + @NonNull UserHandle callingUser, @NonNull UserHandle targetUser); } diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java index e85a70d5845a..69481c32baf0 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java @@ -28,6 +28,7 @@ import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManager.AppFunctionsPolicy; import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchManager.SearchContext; @@ -39,7 +40,6 @@ import android.content.pm.PackageManager; import android.os.Binder; import android.os.Process; import android.os.UserHandle; -import android.os.UserManager; import com.android.internal.infra.AndroidFuture; @@ -124,8 +124,7 @@ class CallerValidatorImpl implements CallerValidator { FutureAppSearchSession futureAppSearchSession = new FutureAppSearchSessionImpl( Objects.requireNonNull( - mContext - .createContextAsUser(targetUser, 0) + mContext.createContextAsUser(targetUser, 0) .getSystemService(AppSearchManager.class)), THREAD_POOL_EXECUTOR, new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build()); @@ -168,13 +167,16 @@ class CallerValidatorImpl implements CallerValidator { } @Override - public boolean isUserOrganizationManaged(@NonNull UserHandle targetUser) { - if (Objects.requireNonNull(mContext.getSystemService(DevicePolicyManager.class)) - .isDeviceManaged()) { - return true; - } - return Objects.requireNonNull(mContext.getSystemService(UserManager.class)) - .isManagedProfile(targetUser.getIdentifier()); + public boolean verifyEnterprisePolicyIsAllowed( + @NonNull UserHandle callingUser, @NonNull UserHandle targetUser) { + @AppFunctionsPolicy + int callingUserPolicy = getDevicePolicyManagerAsUser(callingUser).getAppFunctionsPolicy(); + @AppFunctionsPolicy + int targetUserPolicy = getDevicePolicyManagerAsUser(targetUser).getAppFunctionsPolicy(); + boolean isSameUser = callingUser.equals(targetUser); + + return isAppFunctionPolicyAllowed(targetUserPolicy, isSameUser) + && isAppFunctionPolicyAllowed(callingUserPolicy, isSameUser); } /** @@ -264,4 +266,18 @@ class CallerValidatorImpl implements CallerValidator { return Process.INVALID_UID; } } + + private boolean isAppFunctionPolicyAllowed( + @AppFunctionsPolicy int userPolicy, boolean isSameUser) { + return switch (userPolicy) { + case DevicePolicyManager.APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY -> true; + case DevicePolicyManager.APP_FUNCTIONS_DISABLED_CROSS_PROFILE -> isSameUser; + default -> false; + }; + } + + private DevicePolicyManager getDevicePolicyManagerAsUser(@NonNull UserHandle targetUser) { + return mContext.createContextAsUser(targetUser, /* flags= */ 0) + .getSystemService(DevicePolicyManager.class); + } } -- cgit v1.2.3-59-g8ed1b