summaryrefslogtreecommitdiff
path: root/libs/appfunctions/java
diff options
context:
space:
mode:
author Xin Li <delphij@google.com> 2025-02-27 15:39:31 -0800
committer Xin Li <delphij@google.com> 2025-02-27 15:39:31 -0800
commit82ada6503a81af7eeed2924a2d2d942375f6c8c2 (patch)
treefeb014d10d6fe761610bafd23ed382f0c78084ac /libs/appfunctions/java
parent2b327ef435fd5d687fd5a65c2ee263ba0ebea5ce (diff)
parent450f8ab14dfecc9a5061d30c8e3d6864cc21fb9b (diff)
Merge 25Q1 (ab/12770256) to aosp-main-future
Bug: 385190204 Merged-In: Ibc80f09d54bec2c83814874d94a8f8eb22b5075f Change-Id: If7779ae57fd91631d08bd8af5434c2ddd963aad7
Diffstat (limited to 'libs/appfunctions/java')
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java220
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java197
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java)48
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java)37
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java103
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java)74
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java82
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java240
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 {}
-}