diff options
author | 2025-02-27 15:39:31 -0800 | |
---|---|---|
committer | 2025-02-27 15:39:31 -0800 | |
commit | 82ada6503a81af7eeed2924a2d2d942375f6c8c2 (patch) | |
tree | feb014d10d6fe761610bafd23ed382f0c78084ac /libs/appfunctions/java | |
parent | 2b327ef435fd5d687fd5a65c2ee263ba0ebea5ce (diff) | |
parent | 450f8ab14dfecc9a5061d30c8e3d6864cc21fb9b (diff) |
Merge 25Q1 (ab/12770256) to aosp-main-future
Bug: 385190204
Merged-In: Ibc80f09d54bec2c83814874d94a8f8eb22b5075f
Change-Id: If7779ae57fd91631d08bd8af5434c2ddd963aad7
Diffstat (limited to 'libs/appfunctions/java')
8 files changed, 621 insertions, 380 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..0c521690b165 --- /dev/null +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package 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; + + /** + * The operation was disallowed by enterprise policy. + * + * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; + + /** + * An unknown error occurred while processing the call in the AppFunctionService. + * + * <p>This error is thrown when the service is connected in the remote application but an + * 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) { + super(errorMessage); + 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, + ERROR_ENTERPRISE_POLICY_DISALLOWED + }) + @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 new file mode 100644 index 000000000000..9eb66a33fedc --- /dev/null +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java @@ -0,0 +1,197 @@ +/* + * 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; + +/** + * Provides app functions related functionalities. + * + * <p>App function is a specific piece of functionality that an app offers to the system. These + * functionalities can be integrated into various system features. + * + * <p>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. + * + * <p>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. + * + * <p>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 + OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> + callback) { + Objects.requireNonNull(sidecarRequest); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + android.app.appfunctions.ExecuteAppFunctionRequest platformRequest = + SidecarConverter.getPlatformExecuteAppFunctionRequest(sidecarRequest); + mManager.executeAppFunction( + platformRequest, + executor, + cancellationSignal, + 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)); + } + }); + } + + /** + * Returns a boolean through a callback, indicating whether the app function is enabled. + * + * <p>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<Boolean, Exception> callback) { + mManager.isAppFunctionEnabled(functionIdentifier, targetPackage, executor, callback); + } + + /** + * Returns a boolean through a callback, indicating whether the app function is enabled. + * + * <p>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<Boolean, Exception> callback) { + mManager.isAppFunctionEnabled(functionIdentifier, executor, callback); + } + + /** + * Sets the enabled state of the app function owned by the calling package. + * + * <p>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<Void, Exception> 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/android/extensions/appfunctions/AppFunctionService.java index 65959dfdf561..55f579138218 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; -import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE; +import static com.android.extensions.appfunctions.SidecarConverter.getPlatformAppFunctionException; +import static com.android.extensions.appfunctions.SidecarConverter.getPlatformExecuteAppFunctionResponse; import android.annotation.MainThread; import android.annotation.NonNull; @@ -24,9 +25,9 @@ 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; +import android.os.OutcomeReceiver; /** * Abstract base class to provide app functions to the system. @@ -69,17 +70,29 @@ public abstract class AppFunctionService extends Service { private final Binder mBinder = android.app.appfunctions.AppFunctionService.createBinder( /* context= */ this, - /* onExecuteFunction= */ (platformRequest, callback) -> { + /* onExecuteFunction= */ (platformRequest, + callingPackage, + cancellationSignal, + callback) -> { AppFunctionService.this.onExecuteFunction( SidecarConverter.getSidecarExecuteAppFunctionRequest( platformRequest), - (sidecarResponse) -> { - callback.accept( - SidecarConverter.getPlatformExecuteAppFunctionResponse( - sidecarResponse)); + callingPackage, + cancellationSignal, + new OutcomeReceiver<>() { + @Override + public void onResult(ExecuteAppFunctionResponse result) { + callback.onResult( + getPlatformExecuteAppFunctionResponse(result)); + } + + @Override + public void onError(AppFunctionException exception) { + callback.onError( + getPlatformAppFunctionException(exception)); + } }); - } - ); + }); @NonNull @Override @@ -104,11 +117,20 @@ public abstract class AppFunctionService extends Service { * 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. * + * <p>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 callback A callback to report back the result. + * @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 or error. */ @MainThread public abstract void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, - @NonNull Consumer<ExecuteAppFunctionResponse> callback); + @NonNull String callingPackage, + @NonNull CancellationSignal cancellationSignal, + @NonNull + OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> + callback); } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java index fa6d2ff12313..baddc245f0f1 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; import android.annotation.NonNull; import android.app.appsearch.GenericDocument; @@ -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 { * * <p>The document may have missing parameters. Developers are advised to implement defensive * handling measures. - * - * <p>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. + * + * <p>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. + * + * <p>The ID can be obtained by querying the {@code AppFunctionStaticMetadata} documents from + * AppSearch. + * + * <p>If the {@code functionId} provided is invalid, the caller will get an invalid argument + * response. + */ @NonNull public String getFunctionIdentifier() { return mFunctionIdentifier; @@ -81,8 +91,14 @@ public final class ExecuteAppFunctionRequest { * Returns the function parameters. The key is the parameter name, and the value is the * parameter value. * - * <p>The bundle may have missing parameters. Developers are advised to implement defensive - * handling measures. + * <p>The {@link GenericDocument} may have missing parameters. Developers are advised to + * implement defensive handling measures. + * + * <p>Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be + * obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This + * 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/android/extensions/appfunctions/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java new file mode 100644 index 000000000000..0826f04a50dd --- /dev/null +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.extensions.appfunctions; + +import android.annotation.NonNull; +import android.app.appfunctions.AppFunctionManager; +import android.app.appsearch.GenericDocument; +import android.os.Bundle; + +import java.util.Objects; + +/** The response to an app function execution. */ +public final class ExecuteAppFunctionResponse { + /** + * The name of the property that stores the function return value within the {@code + * resultDocument}. + * + * <p>See {@link GenericDocument#getProperty(String)} for more information. + * + * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will + * be empty {@link GenericDocument}. + * + * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will + * return {@code null}. + * + * <p>See {@link #getResultDocument} for more information on extracting the return value. + */ + public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue"; + + /** + * Returns the return value of the executed function. + * + * <p>The return value is stored in a {@link GenericDocument} with the key {@link + * #PROPERTY_RETURN_VALUE}. + * + * <p>See {@link #getResultDocument} for more information on extracting the return value. + */ + @NonNull private final GenericDocument mResultDocument; + + /** Returns the additional metadata data relevant to this function execution response. */ + @NonNull private final Bundle mExtras; + + /** + * @param resultDocument The return value of the executed function. + */ + public ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument) { + this(resultDocument, Bundle.EMPTY); + } + + /** + * @param resultDocument The return value of the executed function. + * @param extras The additional metadata for this function execution response. + */ + public ExecuteAppFunctionResponse( + @NonNull GenericDocument resultDocument, @NonNull Bundle extras) { + mResultDocument = Objects.requireNonNull(resultDocument); + mExtras = Objects.requireNonNull(extras); + } + + /** + * Returns a generic document containing the return value of the executed function. + * + * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. + * + * <p>Sample code for extracting the return value: + * + * <pre> + * GenericDocument resultDocument = response.getResultDocument(); + * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE); + * if (returnValue != null) { + * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString}, + * // {@link GenericDocument#getPropertyLong} etc. + * // Do something with the returnValue + * } + * </pre> + * + * @see AppFunctionManager on how to determine the expected function return. + */ + @NonNull + public GenericDocument getResultDocument() { + return mResultDocument; + } + + /** Returns the additional metadata for this function execution response. */ + @NonNull + public Bundle getExtras() { + return mExtras; + } +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java index b1b05f79f33f..5e1fc7e684e2 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; import android.annotation.NonNull; @@ -28,46 +28,50 @@ public final class SidecarConverter { private SidecarConverter() {} /** - * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} - * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} + * Converts sidecar's {@link ExecuteAppFunctionRequest} into platform's {@link + * android.app.appfunctions.ExecuteAppFunctionRequest} * * @hide */ @NonNull public static android.app.appfunctions.ExecuteAppFunctionRequest getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) { - return new - android.app.appfunctions.ExecuteAppFunctionRequest.Builder( - request.getTargetPackageName(), - request.getFunctionIdentifier()) + return new android.app.appfunctions.ExecuteAppFunctionRequest.Builder( + request.getTargetPackageName(), request.getFunctionIdentifier()) .setExtras(request.getExtras()) .setParameters(request.getParameters()) .build(); } /** - * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} - * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} + * Converts sidecar's {@link ExecuteAppFunctionResponse} into platform's {@link + * android.app.appfunctions.ExecuteAppFunctionResponse} * * @hide */ @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) { - if (response.isSuccess()) { - return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess( - response.getResultDocument(), response.getExtras()); - } else { - return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure( - response.getResultCode(), - response.getErrorMessage(), - response.getExtras()); - } + return new android.app.appfunctions.ExecuteAppFunctionResponse( + response.getResultDocument(), response.getExtras()); } /** - * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} - * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} + * Converts sidecar's {@link AppFunctionException} into platform's {@link + * android.app.appfunctions.AppFunctionException} + * + * @hide + */ + @NonNull + public static android.app.appfunctions.AppFunctionException + getPlatformAppFunctionException(@NonNull AppFunctionException exception) { + return new android.app.appfunctions.AppFunctionException( + exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras()); + } + + /** + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} into sidecar's + * {@link ExecuteAppFunctionRequest} * * @hide */ @@ -75,30 +79,34 @@ public final class SidecarConverter { public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest( @NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) { return new ExecuteAppFunctionRequest.Builder( - request.getTargetPackageName(), - request.getFunctionIdentifier()) + request.getTargetPackageName(), request.getFunctionIdentifier()) .setExtras(request.getExtras()) .setParameters(request.getParameters()) .build(); } /** - * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} - * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} into + * sidecar's {@link ExecuteAppFunctionResponse} * * @hide */ @NonNull public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse( @NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) { - if (response.isSuccess()) { - return ExecuteAppFunctionResponse.newSuccess( - response.getResultDocument(), response.getExtras()); - } else { - return ExecuteAppFunctionResponse.newFailure( - response.getResultCode(), - response.getErrorMessage(), - response.getExtras()); - } + return new ExecuteAppFunctionResponse(response.getResultDocument(), response.getExtras()); + } + + /** + * Converts platform's {@link android.app.appfunctions.AppFunctionException} into + * sidecar's {@link AppFunctionException} + * + * @hide + */ + @NonNull + public static AppFunctionException getSidecarAppFunctionException( + @NonNull android.app.appfunctions.AppFunctionException exception) { + return new AppFunctionException( + exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras()); } } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java deleted file mode 100644 index b1dd4676a35e..000000000000 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java +++ /dev/null @@ -1,82 +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.CallbackExecutor; -import android.annotation.NonNull; -import android.content.Context; - -import java.util.Objects; -import java.util.concurrent.Executor; -import java.util.function.Consumer; - - -/** - * Provides app functions related functionalities. - * - * <p>App function is a specific piece of functionality that an app offers to the system. These - * functionalities can be integrated into various system features. - * - * <p>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 { - 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. - * - * <p>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. - */ - public void executeAppFunction( - @NonNull ExecuteAppFunctionRequest sidecarRequest, - @NonNull @CallbackExecutor Executor executor, - @NonNull Consumer<ExecuteAppFunctionResponse> callback) { - Objects.requireNonNull(sidecarRequest); - Objects.requireNonNull(executor); - Objects.requireNonNull(callback); - - android.app.appfunctions.ExecuteAppFunctionRequest platformRequest = - SidecarConverter.getPlatformExecuteAppFunctionRequest(sidecarRequest); - mManager.executeAppFunction( - platformRequest, executor, (platformResponse) -> { - callback.accept(SidecarConverter.getSidecarExecuteAppFunctionResponse( - platformResponse)); - }); - } -} 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 60c25fae58d1..000000000000 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.appfunctions.sidecar; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.appsearch.GenericDocument; -import android.os.Bundle; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -/** - * The response to an app function execution. - * - * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel - * functionality and exposes it here as a sidecar library (avoiding direct dependency on the - * platform API). - */ -public final class ExecuteAppFunctionResponse { - /** - * The name of the property that stores the function return value within the {@code - * resultDocument}. - * - * <p>See {@link GenericDocument#getProperty(String)} for more information. - * - * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will - * be empty {@link GenericDocument}. - * - * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will - * return {@code null}. - * - * <p>See {@link #getResultDocument} for more information on extracting the return value. - */ - public static final String PROPERTY_RETURN_VALUE = "returnValue"; - - /** The call was successful. */ - 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; - - /** 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. - * - * <p>This error may be considered similar to {@link IllegalStateException} - */ - public static final int RESULT_INTERNAL_ERROR = 3; - - /** - * The caller supplied invalid arguments to the call. - * - * <p>This error may be considered similar to {@link IllegalArgumentException}. - */ - public static final int RESULT_INVALID_ARGUMENT = 4; - - /** The operation was timed out. */ - public static final int RESULT_TIMED_OUT = 5; - - /** The result code of the app function execution. */ - @ResultCode private final int mResultCode; - - /** - * The error message associated with the result, if any. This is {@code null} if the result code - * is {@link #RESULT_OK}. - */ - @Nullable private final String mErrorMessage; - - /** - * Returns the return value of the executed function. - * - * <p>The return value is stored in a {@link GenericDocument} with the key {@link - * #PROPERTY_RETURN_VALUE}. - * - * <p>See {@link #getResultDocument} for more information on extracting the return value. - */ - @NonNull private final GenericDocument mResultDocument; - - /** Returns the additional metadata data relevant to this function execution response. */ - @NonNull private final Bundle mExtras; - - private ExecuteAppFunctionResponse( - @NonNull GenericDocument resultDocument, - @NonNull Bundle extras, - @ResultCode int resultCode, - @Nullable String errorMessage) { - mResultDocument = Objects.requireNonNull(resultDocument); - mExtras = Objects.requireNonNull(extras); - mResultCode = resultCode; - mErrorMessage = errorMessage; - } - - /** - * Returns result codes from throwable. - * - * @hide - */ - static @ResultCode int getResultCode(@NonNull Throwable t) { - if (t instanceof IllegalArgumentException) { - return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT; - } - return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR; - } - - /** - * Returns a successful response. - * - * @param resultDocument The return value of the executed function. - * @param extras The additional metadata data relevant to this function execution response. - */ - @NonNull - public static ExecuteAppFunctionResponse newSuccess( - @NonNull GenericDocument resultDocument, @Nullable Bundle extras) { - Objects.requireNonNull(resultDocument); - Bundle actualExtras = getActualExtras(extras); - - return new ExecuteAppFunctionResponse( - resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null); - } - - /** - * Returns a failure response. - * - * @param resultCode The result code of the app function execution. - * @param extras The additional metadata data relevant to this function execution response. - * @param errorMessage The error message associated with the result, if any. - */ - @NonNull - public static ExecuteAppFunctionResponse newFailure( - @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) { - if (resultCode == RESULT_OK) { - throw new IllegalArgumentException("resultCode must not be RESULT_OK"); - } - Bundle actualExtras = getActualExtras(extras); - GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build(); - return new ExecuteAppFunctionResponse( - emptyDocument, actualExtras, resultCode, errorMessage); - } - - private static Bundle getActualExtras(@Nullable Bundle extras) { - if (extras == null) { - return Bundle.EMPTY; - } - return extras; - } - - /** - * Returns a generic document containing the return value of the executed function. - * - * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. - * - * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed - * function does not produce a return value. - * - * <p>Sample code for extracting the return value: - * - * <pre> - * GenericDocument resultDocument = response.getResultDocument(); - * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE); - * if (returnValue != null) { - * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString}, - * // {@link GenericDocument#getPropertyLong} etc. - * // Do something with the returnValue - * } - * </pre> - */ - @NonNull - public GenericDocument getResultDocument() { - return mResultDocument; - } - - /** Returns the extras of the app function execution. */ - @NonNull - public Bundle getExtras() { - return mExtras; - } - - /** - * Returns {@code true} if {@link #getResultCode} equals {@link - * ExecuteAppFunctionResponse#RESULT_OK}. - */ - public boolean isSuccess() { - return getResultCode() == RESULT_OK; - } - - /** - * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}. - */ - @ResultCode - public int getResultCode() { - return mResultCode; - } - - /** - * Returns the error message associated with this result. - * - * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. - */ - @Nullable - public String getErrorMessage() { - return mErrorMessage; - } - - /** - * Result codes. - * - * @hide - */ - @IntDef( - prefix = {"RESULT_"}, - value = { - RESULT_OK, - RESULT_DENIED, - RESULT_APP_UNKNOWN_ERROR, - RESULT_INTERNAL_ERROR, - RESULT_INVALID_ARGUMENT, - RESULT_TIMED_OUT, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ResultCode {} -} |