diff options
author | 2024-11-07 23:07:21 +0000 | |
---|---|---|
committer | 2024-11-07 23:42:32 +0000 | |
commit | fdd0e6bbee3fd65df17c0ac8486c24015f9cb072 (patch) | |
tree | 969f57797cc013557296f8b77bffaad818598f97 /libs/appfunctions/java | |
parent | 737d9f3a66ee458be789759078b1c42843857a68 (diff) |
Update executeAppFunction to take a OutcomeReceiver
Flag: android.app.appfunctions.flags.enable_app_function_manager
Test: Existing CTS
Bug: 376426049
Change-Id: Icec71cdcb79d3f22854cd4c28b65ca6d56d48883
Diffstat (limited to 'libs/appfunctions/java')
5 files changed, 289 insertions, 298 deletions
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java new file mode 100644 index 000000000000..28c3b3df9b1c --- /dev/null +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.extensions.appfunctions; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Represents an app function related errors. */ +public final class AppFunctionException extends Exception { + /** + * The caller does not have the permission to execute an app function. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_DENIED = 1000; + + /** + * The caller supplied invalid arguments to the execution request. + * + * <p>This error may be considered similar to {@link IllegalArgumentException}. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_INVALID_ARGUMENT = 1001; + + /** + * The caller tried to execute a disabled app function. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_DISABLED = 1002; + + /** + * The caller tried to execute a function that does not exist. + * + * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. + */ + public static final int ERROR_FUNCTION_NOT_FOUND = 1003; + + /** + * An internal unexpected error coming from the system. + * + * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_SYSTEM_ERROR = 2000; + + /** + * The operation was cancelled. Use this error code to report that a cancellation is done after + * receiving a cancellation signal. + * + * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_CANCELLED = 2001; + + /** + * An unknown error occurred while processing the call in the AppFunctionService. + * + * <p>This error is thrown when the service is connected in the remote application but an + * unexpected error is thrown from the bound application. + * + * <p>This error is in the {@link #ERROR_CATEGORY_APP} category. + */ + public static final int ERROR_APP_UNKNOWN_ERROR = 3000; + + /** + * The error category is unknown. + * + * <p>This is the default value for {@link #getErrorCategory}. + */ + public static final int ERROR_CATEGORY_UNKNOWN = 0; + + /** + * The error is caused by the app requesting a function execution. + * + * <p>For example, the caller provided invalid parameters in the execution request e.g. an + * invalid function ID. + * + * <p>Errors in the category fall in the range 1000-1999 inclusive. + */ + public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; + + /** + * The error is caused by an issue in the system. + * + * <p>For example, the AppFunctionService implementation is not found by the system. + * + * <p>Errors in the category fall in the range 2000-2999 inclusive. + */ + public static final int ERROR_CATEGORY_SYSTEM = 2; + + /** + * The error is caused by the app providing the function. + * + * <p>For example, the app crashed when the system is executing the request. + * + * <p>Errors in the category fall in the range 3000-3999 inclusive. + */ + public static final int ERROR_CATEGORY_APP = 3; + + private final int mErrorCode; + @Nullable private final String mErrorMessage; + @NonNull private final Bundle mExtras; + + public AppFunctionException(int errorCode, @Nullable String errorMessage) { + this(errorCode, errorMessage, Bundle.EMPTY); + } + + public AppFunctionException( + int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) { + mErrorCode = errorCode; + mErrorMessage = errorMessage; + mExtras = extras; + } + + /** Returns one of the {@code ERROR} constants. */ + @ErrorCode + public int getErrorCode() { + return mErrorCode; + } + + /** Returns the error message. */ + @Nullable + public String getErrorMessage() { + return mErrorMessage; + } + + /** + * Returns the error category. + * + * <p>This method categorizes errors based on their underlying cause, allowing developers to + * implement targeted error handling and provide more informative error messages to users. It + * maps ranges of error codes to specific error categories. + * + * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the error code does not belong to + * any error category. + * + * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding + * error code ranges. + */ + @ErrorCategory + public int getErrorCategory() { + if (mErrorCode >= 1000 && mErrorCode < 2000) { + return ERROR_CATEGORY_REQUEST_ERROR; + } + if (mErrorCode >= 2000 && mErrorCode < 3000) { + return ERROR_CATEGORY_SYSTEM; + } + if (mErrorCode >= 3000 && mErrorCode < 4000) { + return ERROR_CATEGORY_APP; + } + return ERROR_CATEGORY_UNKNOWN; + } + + @NonNull + public Bundle getExtras() { + return mExtras; + } + + /** + * Error codes. + * + * @hide + */ + @IntDef( + prefix = {"ERROR_"}, + value = { + ERROR_DENIED, + ERROR_APP_UNKNOWN_ERROR, + ERROR_FUNCTION_NOT_FOUND, + ERROR_SYSTEM_ERROR, + ERROR_INVALID_ARGUMENT, + ERROR_DISABLED, + ERROR_CANCELLED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ErrorCode {} + + /** + * Error categories. + * + * @hide + */ + @IntDef( + prefix = {"ERROR_CATEGORY_"}, + value = { + ERROR_CATEGORY_UNKNOWN, + ERROR_CATEGORY_REQUEST_ERROR, + ERROR_CATEGORY_APP, + ERROR_CATEGORY_SYSTEM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ErrorCategory {} +} diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java index d64593d8ff5f..9eb66a33fedc 100644 --- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java @@ -31,7 +31,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.concurrent.Executor; -import java.util.function.Consumer; /** * Provides app functions related functionalities. @@ -115,7 +114,9 @@ public final class AppFunctionManager { @NonNull ExecuteAppFunctionRequest sidecarRequest, @NonNull @CallbackExecutor Executor executor, @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer<ExecuteAppFunctionResponse> callback) { + @NonNull + OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> + callback) { Objects.requireNonNull(sidecarRequest); Objects.requireNonNull(executor); Objects.requireNonNull(callback); @@ -126,10 +127,20 @@ public final class AppFunctionManager { platformRequest, executor, cancellationSignal, - (platformResponse) -> { - callback.accept( - SidecarConverter.getSidecarExecuteAppFunctionResponse( - platformResponse)); + new OutcomeReceiver<>() { + @Override + public void onResult( + android.app.appfunctions.ExecuteAppFunctionResponse result) { + callback.onResult( + SidecarConverter.getSidecarExecuteAppFunctionResponse(result)); + } + + @Override + public void onError( + android.app.appfunctions.AppFunctionException exception) { + callback.onError( + SidecarConverter.getSidecarAppFunctionException(exception)); + } }); } diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java index 1a4d9da8bd63..55f579138218 100644 --- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java @@ -16,7 +16,8 @@ package com.android.extensions.appfunctions; -import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE; +import static com.android.extensions.appfunctions.SidecarConverter.getPlatformAppFunctionException; +import static com.android.extensions.appfunctions.SidecarConverter.getPlatformExecuteAppFunctionResponse; import android.annotation.MainThread; import android.annotation.NonNull; @@ -26,8 +27,7 @@ import android.content.Intent; import android.os.Binder; import android.os.CancellationSignal; import android.os.IBinder; - -import java.util.function.Consumer; +import android.os.OutcomeReceiver; /** * Abstract base class to provide app functions to the system. @@ -79,10 +79,18 @@ public abstract class AppFunctionService extends Service { platformRequest), callingPackage, cancellationSignal, - (sidecarResponse) -> { - callback.accept( - SidecarConverter.getPlatformExecuteAppFunctionResponse( - sidecarResponse)); + new OutcomeReceiver<>() { + @Override + public void onResult(ExecuteAppFunctionResponse result) { + callback.onResult( + getPlatformExecuteAppFunctionResponse(result)); + } + + @Override + public void onError(AppFunctionException exception) { + callback.onError( + getPlatformAppFunctionException(exception)); + } }); }); @@ -115,12 +123,14 @@ public abstract class AppFunctionService extends Service { * @param request The function execution request. * @param callingPackage The package name of the app that is requesting the execution. * @param cancellationSignal A signal to cancel the execution. - * @param callback A callback to report back the result. + * @param callback A callback to report back the result or error. */ @MainThread public abstract void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull String callingPackage, @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer<ExecuteAppFunctionResponse> callback); + @NonNull + OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> + callback); } diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java index 7c5ddcd9edfb..42c3c033ad38 100644 --- a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java @@ -16,23 +16,14 @@ package com.android.extensions.appfunctions; -import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; +import android.app.appfunctions.AppFunctionManager; import android.app.appsearch.GenericDocument; import android.os.Bundle; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.Objects; -/** - * The response to an app function execution. - * - * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel - * functionality and exposes it here as a sidecar library (avoiding direct dependency on the - * platform API). - */ +/** The response to an app function execution. */ public final class ExecuteAppFunctionResponse { /** * The name of the property that stores the function return value within the {@code @@ -51,112 +42,6 @@ public final class ExecuteAppFunctionResponse { public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; /** - * The call was successful. - * - * <p>This result code does not belong in an error category. - */ - public static final int RESULT_OK = 0; - - /** - * The caller does not have the permission to execute an app function. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_DENIED = 1000; - - /** - * The caller supplied invalid arguments to the execution request. - * - * <p>This error may be considered similar to {@link IllegalArgumentException}. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_INVALID_ARGUMENT = 1001; - - /** - * The caller tried to execute a disabled app function. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_DISABLED = 1002; - - /** - * The caller tried to execute a function that does not exist. - * - * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category. - */ - public static final int RESULT_FUNCTION_NOT_FOUND = 1003; - - /** - * An internal unexpected error coming from the system. - * - * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. - */ - public static final int RESULT_SYSTEM_ERROR = 2000; - - /** - * The operation was cancelled. Use this error code to report that a cancellation is done after - * receiving a cancellation signal. - * - * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. - */ - public static final int RESULT_CANCELLED = 2001; - - /** - * An unknown error occurred while processing the call in the AppFunctionService. - * - * <p>This error is thrown when the service is connected in the remote application but an - * unexpected error is thrown from the bound application. - * - * <p>This error is in the {@link #ERROR_CATEGORY_APP} category. - */ - public static final int RESULT_APP_UNKNOWN_ERROR = 3000; - - /** - * The error category is unknown. - * - * <p>This is the default value for {@link #getErrorCategory}. - */ - public static final int ERROR_CATEGORY_UNKNOWN = 0; - - /** - * The error is caused by the app requesting a function execution. - * - * <p>For example, the caller provided invalid parameters in the execution request e.g. an - * invalid function ID. - * - * <p>Errors in the category fall in the range 1000-1999 inclusive. - */ - public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; - - /** - * The error is caused by an issue in the system. - * - * <p>For example, the AppFunctionService implementation is not found by the system. - * - * <p>Errors in the category fall in the range 2000-2999 inclusive. - */ - public static final int ERROR_CATEGORY_SYSTEM = 2; - - /** - * The error is caused by the app providing the function. - * - * <p>For example, the app crashed when the system is executing the request. - * - * <p>Errors in the category fall in the range 3000-3999 inclusive. - */ - public static final int ERROR_CATEGORY_APP = 3; - - /** The result code of the app function execution. */ - @ResultCode private final int mResultCode; - - /** - * The error message associated with the result, if any. This is {@code null} if the result code - * is {@link #RESULT_OK}. - */ - @Nullable private final String mErrorMessage; - - /** * Returns the return value of the executed function. * * <p>The return value is stored in a {@link GenericDocument} with the key {@link @@ -169,99 +54,21 @@ public final class ExecuteAppFunctionResponse { /** Returns the additional metadata data relevant to this function execution response. */ @NonNull private final Bundle mExtras; - private ExecuteAppFunctionResponse( - @NonNull GenericDocument resultDocument, - @NonNull Bundle extras, - @ResultCode int resultCode, - @Nullable String errorMessage) { - mResultDocument = Objects.requireNonNull(resultDocument); - mExtras = Objects.requireNonNull(extras); - mResultCode = resultCode; - mErrorMessage = errorMessage; - } - /** - * Returns result codes from throwable. - * - * @hide - */ - static @ResultCode int getResultCode(@NonNull Throwable t) { - if (t instanceof IllegalArgumentException) { - return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT; - } - return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR; - } - - /** - * Returns a successful response. - * * @param resultDocument The return value of the executed function. - * @param extras The additional metadata data relevant to this function execution response. */ - @NonNull - public static ExecuteAppFunctionResponse newSuccess( - @NonNull GenericDocument resultDocument, @Nullable Bundle extras) { - Objects.requireNonNull(resultDocument); - Bundle actualExtras = getActualExtras(extras); - - return new ExecuteAppFunctionResponse( - resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null); + public ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument) { + this(resultDocument, Bundle.EMPTY); } /** - * Returns a failure response. - * - * @param resultCode The result code of the app function execution. - * @param extras The additional metadata data relevant to this function execution response. - * @param errorMessage The error message associated with the result, if any. - */ - @NonNull - public static ExecuteAppFunctionResponse newFailure( - @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) { - if (resultCode == RESULT_OK) { - throw new IllegalArgumentException("resultCode must not be RESULT_OK"); - } - Bundle actualExtras = getActualExtras(extras); - GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build(); - return new ExecuteAppFunctionResponse( - emptyDocument, actualExtras, resultCode, errorMessage); - } - - private static Bundle getActualExtras(@Nullable Bundle extras) { - if (extras == null) { - return Bundle.EMPTY; - } - return extras; - } - - /** - * Returns the error category of the {@link ExecuteAppFunctionResponse}. - * - * <p>This method categorizes errors based on their underlying cause, allowing developers to - * implement targeted error handling and provide more informative error messages to users. It - * maps ranges of result codes to specific error categories. - * - * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to - * ensure correct categorization of the failed response. - * - * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to - * any error category, for example, in the case of a successful result with {@link #RESULT_OK}. - * - * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding - * result code ranges. + * @param resultDocument The return value of the executed function. + * @param extras The additional metadata for this function execution response. */ - @ErrorCategory - public int getErrorCategory() { - if (mResultCode >= 1000 && mResultCode < 2000) { - return ERROR_CATEGORY_REQUEST_ERROR; - } - if (mResultCode >= 2000 && mResultCode < 3000) { - return ERROR_CATEGORY_SYSTEM; - } - if (mResultCode >= 3000 && mResultCode < 4000) { - return ERROR_CATEGORY_APP; - } - return ERROR_CATEGORY_UNKNOWN; + public ExecuteAppFunctionResponse( + @NonNull GenericDocument resultDocument, @NonNull Bundle extras) { + mResultDocument = Objects.requireNonNull(resultDocument); + mExtras = Objects.requireNonNull(extras); } /** @@ -269,9 +76,6 @@ public final class ExecuteAppFunctionResponse { * * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. * - * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed - * function does not produce a return value. - * * <p>Sample code for extracting the return value: * * <pre> @@ -283,77 +87,17 @@ public final class ExecuteAppFunctionResponse { * // Do something with the returnValue * } * </pre> + * + * @see AppFunctionManager on how to determine the expected function return. */ @NonNull public GenericDocument getResultDocument() { return mResultDocument; } - /** Returns the extras of the app function execution. */ + /** Returns the additional metadata for this function execution response. */ @NonNull public Bundle getExtras() { return mExtras; } - - /** - * Returns {@code true} if {@link #getResultCode} equals {@link - * ExecuteAppFunctionResponse#RESULT_OK}. - */ - public boolean isSuccess() { - return getResultCode() == RESULT_OK; - } - - /** - * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}. - */ - @ResultCode - public int getResultCode() { - return mResultCode; - } - - /** - * Returns the error message associated with this result. - * - * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. - */ - @Nullable - public String getErrorMessage() { - return mErrorMessage; - } - - /** - * Result codes. - * - * @hide - */ - @IntDef( - prefix = {"RESULT_"}, - value = { - RESULT_OK, - RESULT_DENIED, - RESULT_APP_UNKNOWN_ERROR, - RESULT_SYSTEM_ERROR, - RESULT_FUNCTION_NOT_FOUND, - RESULT_INVALID_ARGUMENT, - RESULT_DISABLED, - RESULT_CANCELLED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ResultCode {} - - /** - * Error categories. - * - * @hide - */ - @IntDef( - prefix = {"ERROR_CATEGORY_"}, - value = { - ERROR_CATEGORY_UNKNOWN, - ERROR_CATEGORY_REQUEST_ERROR, - ERROR_CATEGORY_APP, - ERROR_CATEGORY_SYSTEM - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ErrorCategory {} } diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java index 56f2725fccc7..5e1fc7e684e2 100644 --- a/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java @@ -52,13 +52,21 @@ public final class SidecarConverter { @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) { - if (response.isSuccess()) { - return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess( - response.getResultDocument(), response.getExtras()); - } else { - return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure( - response.getResultCode(), response.getErrorMessage(), response.getExtras()); - } + return new android.app.appfunctions.ExecuteAppFunctionResponse( + response.getResultDocument(), response.getExtras()); + } + + /** + * Converts sidecar's {@link AppFunctionException} into platform's {@link + * android.app.appfunctions.AppFunctionException} + * + * @hide + */ + @NonNull + public static android.app.appfunctions.AppFunctionException + getPlatformAppFunctionException(@NonNull AppFunctionException exception) { + return new android.app.appfunctions.AppFunctionException( + exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras()); } /** @@ -86,12 +94,19 @@ public final class SidecarConverter { @NonNull public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse( @NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) { - if (response.isSuccess()) { - return ExecuteAppFunctionResponse.newSuccess( - response.getResultDocument(), response.getExtras()); - } else { - return ExecuteAppFunctionResponse.newFailure( - response.getResultCode(), response.getErrorMessage(), response.getExtras()); - } + return new ExecuteAppFunctionResponse(response.getResultDocument(), response.getExtras()); + } + + /** + * Converts platform's {@link android.app.appfunctions.AppFunctionException} into + * sidecar's {@link AppFunctionException} + * + * @hide + */ + @NonNull + public static AppFunctionException getSidecarAppFunctionException( + @NonNull android.app.appfunctions.AppFunctionException exception) { + return new AppFunctionException( + exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras()); } } |