Add AppFunctions Sidecar
Test: atest CtsAppFunctionTestCases
Flag: NONE sidecar library (non platform)
Change-Id: I2f8af326a2852314ab4ed9b54166024a8888dc63
diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp
new file mode 100644
index 0000000..09e2f42
--- /dev/null
+++ b/libs/appfunctions/Android.bp
@@ -0,0 +1,31 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_sdk_library {
+ name: "com.google.android.appfunctions.sidecar",
+ owner: "google",
+ srcs: ["java/**/*.java"],
+ api_packages: ["com.google.android.appfunctions.sidecar"],
+ dex_preopt: {
+ enabled: false,
+ },
+ system_ext_specific: true,
+ no_dist: true,
+ unsafe_ignore_missing_latest_api: true,
+}
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
new file mode 100644
index 0000000..504e329
--- /dev/null
+++ b/libs/appfunctions/api/current.txt
@@ -0,0 +1,49 @@
+// Signature format: 2.0
+package com.google.android.appfunctions.sidecar {
+
+ public final class AppFunctionManager {
+ ctor public AppFunctionManager(android.content.Context);
+ method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ }
+
+ public abstract class AppFunctionService extends android.app.Service {
+ ctor public AppFunctionService();
+ method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
+ field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
+ }
+
+ public final class ExecuteAppFunctionRequest {
+ method @NonNull public android.os.Bundle getExtras();
+ method @NonNull public String getFunctionIdentifier();
+ method @NonNull public android.app.appsearch.GenericDocument getParameters();
+ method @NonNull public String getTargetPackageName();
+ }
+
+ public static final class ExecuteAppFunctionRequest.Builder {
+ ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String);
+ method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest build();
+ method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
+ }
+
+ public final class ExecuteAppFunctionResponse {
+ method @Nullable public String getErrorMessage();
+ method @NonNull public android.os.Bundle getExtras();
+ method public int getResultCode();
+ method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
+ method public boolean isSuccess();
+ method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
+ method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
+ field public static final String PROPERTY_RETURN_VALUE = "returnValue";
+ field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
+ field public static final int RESULT_DENIED = 1; // 0x1
+ field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
+ field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+ field public static final int RESULT_OK = 0; // 0x0
+ field public static final int RESULT_TIMED_OUT = 5; // 0x5
+ }
+
+}
+
diff --git a/libs/appfunctions/api/removed.txt b/libs/appfunctions/api/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/api/system-current.txt b/libs/appfunctions/api/system-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/system-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/api/system-removed.txt b/libs/appfunctions/api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/api/test-current.txt b/libs/appfunctions/api/test-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/test-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/api/test-removed.txt b/libs/appfunctions/api/test-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/test-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
new file mode 100644
index 0000000..b1dd467
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
@@ -0,0 +1,82 @@
+/*
+ * 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/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
new file mode 100644
index 0000000..65959df
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.appfunctions.sidecar;
+
+import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+import java.util.function.Consumer;
+
+/**
+ * Abstract base class to provide app functions to the system.
+ *
+ * <p>Include the following in the manifest:
+ *
+ * <pre>
+ * {@literal
+ * <service android:name=".YourService"
+ * android:permission="android.permission.BIND_APP_FUNCTION_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.app.appfunctions.AppFunctionService" />
+ * </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ *
+ * <p>This class wraps {@link android.app.appfunctions.AppFunctionService} functionalities and
+ * exposes it here as a sidecar library (avoiding direct dependency on the platform API).
+ *
+ * @see AppFunctionManager
+ */
+public abstract class AppFunctionService extends Service {
+ /**
+ * The permission to only allow system access to the functions through {@link
+ * AppFunctionManagerService}.
+ */
+ @NonNull
+ public static final String BIND_APP_FUNCTION_SERVICE =
+ "android.permission.BIND_APP_FUNCTION_SERVICE";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service. To be supported, the
+ * service must also require the {@link BIND_APP_FUNCTION_SERVICE} permission so that other
+ * applications can not abuse it.
+ */
+ @NonNull
+ public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
+
+ private final Binder mBinder =
+ android.app.appfunctions.AppFunctionService.createBinder(
+ /* context= */ this,
+ /* onExecuteFunction= */ (platformRequest, callback) -> {
+ AppFunctionService.this.onExecuteFunction(
+ SidecarConverter.getSidecarExecuteAppFunctionRequest(
+ platformRequest),
+ (sidecarResponse) -> {
+ callback.accept(
+ SidecarConverter.getPlatformExecuteAppFunctionResponse(
+ sidecarResponse));
+ });
+ }
+ );
+
+ @NonNull
+ @Override
+ public final IBinder onBind(@Nullable Intent intent) {
+ return mBinder;
+ }
+
+ /**
+ * Called by the system to execute a specific app function.
+ *
+ * <p>This method is triggered when the system requests your AppFunctionService to handle a
+ * particular function you have registered and made available.
+ *
+ * <p>To ensure proper routing of function requests, assign a unique identifier to each
+ * function. This identifier doesn't need to be globally unique, but it must be unique within
+ * your app. For example, a function to order food could be identified as "orderFood". In most
+ * cases this identifier should come from the ID automatically generated by the AppFunctions
+ * SDK. You can determine the specific function to invoke by calling {@link
+ * ExecuteAppFunctionRequest#getFunctionIdentifier()}.
+ *
+ * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
+ * thread and dispatch the result with the given callback. You should always report back the
+ * result using the callback, no matter if the execution was successful or not.
+ *
+ * @param request The function execution request.
+ * @param callback A callback to report back the result.
+ */
+ @MainThread
+ public abstract void onExecuteFunction(
+ @NonNull ExecuteAppFunctionRequest request,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
new file mode 100644
index 0000000..fa6d2ff
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.appfunctions.sidecar;
+
+import android.annotation.NonNull;
+import android.app.appsearch.GenericDocument;
+import android.os.Bundle;
+
+import java.util.Objects;
+
+/**
+ * A request to execute an app function.
+ *
+ * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionRequest} without parcel
+ * functionality and exposes it here as a sidecar library (avoiding direct dependency on the
+ * platform API).
+ */
+public final class ExecuteAppFunctionRequest {
+ /** Returns the package name of the app that hosts the function. */
+ @NonNull private final String mTargetPackageName;
+
+ /**
+ * Returns the unique string identifier of the app function to be executed. TODO(b/357551503):
+ * Document how callers can get the available function identifiers.
+ */
+ @NonNull private final String mFunctionIdentifier;
+
+ /** Returns additional metadata relevant to this function execution request. */
+ @NonNull private final Bundle mExtras;
+
+ /**
+ * Returns the parameters required to invoke this function. Within this [GenericDocument], the
+ * property names are the names of the function parameters and the property values are the
+ * values of those parameters.
+ *
+ * <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;
+
+ private ExecuteAppFunctionRequest(
+ @NonNull String targetPackageName,
+ @NonNull String functionIdentifier,
+ @NonNull Bundle extras,
+ @NonNull GenericDocument parameters) {
+ mTargetPackageName = Objects.requireNonNull(targetPackageName);
+ mFunctionIdentifier = Objects.requireNonNull(functionIdentifier);
+ mExtras = Objects.requireNonNull(extras);
+ mParameters = Objects.requireNonNull(parameters);
+ }
+
+ /** Returns the package name of the app that hosts the function. */
+ @NonNull
+ public String getTargetPackageName() {
+ return mTargetPackageName;
+ }
+
+ /** Returns the unique string identifier of the app function to be executed. */
+ @NonNull
+ public String getFunctionIdentifier() {
+ return mFunctionIdentifier;
+ }
+
+ /**
+ * 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.
+ */
+ @NonNull
+ public GenericDocument getParameters() {
+ return mParameters;
+ }
+
+ /** Returns the additional data relevant to this function execution. */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /** Builder for {@link ExecuteAppFunctionRequest}. */
+ public static final class Builder {
+ @NonNull private final String mTargetPackageName;
+ @NonNull private final String mFunctionIdentifier;
+ @NonNull private Bundle mExtras = Bundle.EMPTY;
+
+ @NonNull
+ private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build();
+
+ public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) {
+ mTargetPackageName = Objects.requireNonNull(targetPackageName);
+ mFunctionIdentifier = Objects.requireNonNull(functionIdentifier);
+ }
+
+ /** Sets the additional data relevant to this function execution. */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = Objects.requireNonNull(extras);
+ return this;
+ }
+
+ /** Sets the function parameters. */
+ @NonNull
+ public Builder setParameters(@NonNull GenericDocument parameters) {
+ Objects.requireNonNull(parameters);
+ mParameters = parameters;
+ return this;
+ }
+
+ /** Builds the {@link ExecuteAppFunctionRequest}. */
+ @NonNull
+ public ExecuteAppFunctionRequest build() {
+ return new ExecuteAppFunctionRequest(
+ mTargetPackageName,
+ mFunctionIdentifier,
+ mExtras,
+ mParameters);
+ }
+ }
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
new file mode 100644
index 0000000..60c25fa
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -0,0 +1,240 @@
+/*
+ * 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 {}
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java
new file mode 100644
index 0000000..b1b05f7
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.appfunctions.sidecar;
+
+import android.annotation.NonNull;
+
+/**
+ * Utility class containing methods to convert Sidecar objects of AppFunctions API into the
+ * underlying platform classes.
+ *
+ * @hide
+ */
+public final class SidecarConverter {
+ private SidecarConverter() {}
+
+ /**
+ * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
+ * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
+ *
+ * @hide
+ */
+ @NonNull
+ public static android.app.appfunctions.ExecuteAppFunctionRequest
+ getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) {
+ return new
+ android.app.appfunctions.ExecuteAppFunctionRequest.Builder(
+ request.getTargetPackageName(),
+ request.getFunctionIdentifier())
+ .setExtras(request.getExtras())
+ .setParameters(request.getParameters())
+ .build();
+ }
+
+ /**
+ * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
+ * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
+ *
+ * @hide
+ */
+ @NonNull
+ public static android.app.appfunctions.ExecuteAppFunctionResponse
+ getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) {
+ if (response.isSuccess()) {
+ return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess(
+ response.getResultDocument(), response.getExtras());
+ } else {
+ return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure(
+ response.getResultCode(),
+ response.getErrorMessage(),
+ response.getExtras());
+ }
+ }
+
+ /**
+ * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
+ * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
+ *
+ * @hide
+ */
+ @NonNull
+ public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest(
+ @NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) {
+ return new ExecuteAppFunctionRequest.Builder(
+ request.getTargetPackageName(),
+ request.getFunctionIdentifier())
+ .setExtras(request.getExtras())
+ .setParameters(request.getParameters())
+ .build();
+ }
+
+ /**
+ * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
+ * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
+ *
+ * @hide
+ */
+ @NonNull
+ public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse(
+ @NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) {
+ if (response.isSuccess()) {
+ return ExecuteAppFunctionResponse.newSuccess(
+ response.getResultDocument(), response.getExtras());
+ } else {
+ return ExecuteAppFunctionResponse.newFailure(
+ response.getResultCode(),
+ response.getErrorMessage(),
+ response.getExtras());
+ }
+ }
+}