summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt47
-rw-r--r--core/java/android/app/appfunctions/AppFunctionException.aidl21
-rw-r--r--core/java/android/app/appfunctions/AppFunctionException.java260
-rw-r--r--core/java/android/app/appfunctions/AppFunctionManager.java38
-rw-r--r--core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java4
-rw-r--r--core/java/android/app/appfunctions/AppFunctionService.java44
-rw-r--r--core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java2
-rw-r--r--core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java277
-rw-r--r--core/java/android/app/appfunctions/GenericDocumentWrapper.java11
-rw-r--r--core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl4
-rw-r--r--core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java42
-rw-r--r--libs/appfunctions/api/current.txt44
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java211
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java23
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java28
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java282
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java43
-rw-r--r--libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt73
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java64
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java26
20 files changed, 788 insertions, 756 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 664b3dd125ef..00196ee376e5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8790,8 +8790,31 @@ package android.app.admin {
package android.app.appfunctions {
+ @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionException extends java.lang.Exception implements android.os.Parcelable {
+ ctor public AppFunctionException(int, @Nullable String);
+ ctor public AppFunctionException(int, @Nullable String, @NonNull android.os.Bundle);
+ method public int describeContents();
+ method public int getErrorCategory();
+ method public int getErrorCode();
+ method @Nullable public String getErrorMessage();
+ method @NonNull public android.os.Bundle getExtras();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.AppFunctionException> CREATOR;
+ field public static final int ERROR_APP_UNKNOWN_ERROR = 3000; // 0xbb8
+ field public static final int ERROR_CANCELLED = 2001; // 0x7d1
+ field public static final int ERROR_CATEGORY_APP = 3; // 0x3
+ field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
+ field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
+ field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
+ field public static final int ERROR_DENIED = 1000; // 0x3e8
+ field public static final int ERROR_DISABLED = 1002; // 0x3ea
+ field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb
+ field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9
+ field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0
+ }
+
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
- method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
@@ -8803,7 +8826,7 @@ package android.app.appfunctions {
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -8825,30 +8848,14 @@ package android.app.appfunctions {
}
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionResponse implements android.os.Parcelable {
+ ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument);
+ ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle);
method public int describeContents();
- method public int getErrorCategory();
- method @Nullable public String getErrorMessage();
method @NonNull public android.os.Bundle getExtras();
- method public int getResultCode();
method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
- method public boolean isSuccess();
- method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
- method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
- field public static final int ERROR_CATEGORY_APP = 3; // 0x3
- field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
- field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
- field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
- field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8
- field public static final int RESULT_CANCELLED = 2001; // 0x7d1
- field public static final int RESULT_DENIED = 1000; // 0x3e8
- field public static final int RESULT_DISABLED = 1002; // 0x3ea
- field public static final int RESULT_FUNCTION_NOT_FOUND = 1003; // 0x3eb
- field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9
- field public static final int RESULT_OK = 0; // 0x0
- field public static final int RESULT_SYSTEM_ERROR = 2000; // 0x7d0
}
}
diff --git a/core/java/android/app/appfunctions/AppFunctionException.aidl b/core/java/android/app/appfunctions/AppFunctionException.aidl
new file mode 100644
index 000000000000..7d432243b1b2
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionException.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package android.app.appfunctions;
+
+import android.app.appfunctions.AppFunctionException;
+
+parcelable AppFunctionException; \ No newline at end of file
diff --git a/core/java/android/app/appfunctions/AppFunctionException.java b/core/java/android/app/appfunctions/AppFunctionException.java
new file mode 100644
index 000000000000..d33b5055f9cc
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionException.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/** Represents an app function related errors. */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public final class AppFunctionException extends Exception implements Parcelable {
+ /**
+ * The caller does not have the permission to execute an app function.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_DENIED = 1000;
+
+ /**
+ * The caller supplied invalid arguments to the execution request.
+ *
+ * <p>This error may be considered similar to {@link IllegalArgumentException}.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_INVALID_ARGUMENT = 1001;
+
+ /**
+ * The caller tried to execute a disabled app function.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_DISABLED = 1002;
+
+ /**
+ * The caller tried to execute a function that does not exist.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_FUNCTION_NOT_FOUND = 1003;
+
+ /**
+ * An internal unexpected error coming from the system.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+ */
+ public static final int ERROR_SYSTEM_ERROR = 2000;
+
+ /**
+ * The operation was cancelled. Use this error code to report that a cancellation is done after
+ * receiving a cancellation signal.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+ */
+ public static final int ERROR_CANCELLED = 2001;
+
+ /**
+ * An unknown error occurred while processing the call in the AppFunctionService.
+ *
+ * <p>This error is thrown when the service is connected in the remote application but an
+ * unexpected error is thrown from the bound application.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
+ */
+ public static final int ERROR_APP_UNKNOWN_ERROR = 3000;
+
+ /**
+ * The error category is unknown.
+ *
+ * <p>This is the default value for {@link #getErrorCategory}.
+ */
+ public static final int ERROR_CATEGORY_UNKNOWN = 0;
+
+ /**
+ * The error is caused by the app requesting a function execution.
+ *
+ * <p>For example, the caller provided invalid parameters in the execution request e.g. an
+ * invalid function ID.
+ *
+ * <p>Errors in the category fall in the range 1000-1999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
+
+ /**
+ * The error is caused by an issue in the system.
+ *
+ * <p>For example, the AppFunctionService implementation is not found by the system.
+ *
+ * <p>Errors in the category fall in the range 2000-2999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_SYSTEM = 2;
+
+ /**
+ * The error is caused by the app providing the function.
+ *
+ * <p>For example, the app crashed when the system is executing the request.
+ *
+ * <p>Errors in the category fall in the range 3000-3999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_APP = 3;
+
+ private final int mErrorCode;
+ @Nullable private final String mErrorMessage;
+ @NonNull private final Bundle mExtras;
+
+ /**
+ * @param errorCode The error code.
+ * @param errorMessage The error message.
+ */
+ public AppFunctionException(@ErrorCode int errorCode, @Nullable String errorMessage) {
+ this(errorCode, errorMessage, Bundle.EMPTY);
+ }
+
+ /**
+ * @param errorCode The error code.
+ * @param errorMessage The error message.
+ * @param extras The extras associated with this error.
+ */
+ public AppFunctionException(
+ @ErrorCode int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) {
+ mErrorCode = errorCode;
+ mErrorMessage = errorMessage;
+ mExtras = Objects.requireNonNull(extras);
+ }
+
+ private AppFunctionException(@NonNull Parcel in) {
+ mErrorCode = in.readInt();
+ mErrorMessage = in.readString8();
+ mExtras = Objects.requireNonNull(in.readBundle(getClass().getClassLoader()));
+ }
+
+ /** Returns one of the {@code ERROR} constants. */
+ @ErrorCode
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /** Returns the error message. */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /**
+ * Returns the error category.
+ *
+ * <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;
+ }
+
+ /** Returns any extras associated with this error. */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mErrorCode);
+ dest.writeString8(mErrorMessage);
+ dest.writeBundle(mExtras);
+ }
+
+ /**
+ * Error codes.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"ERROR_"},
+ value = {
+ ERROR_DENIED,
+ ERROR_APP_UNKNOWN_ERROR,
+ ERROR_FUNCTION_NOT_FOUND,
+ ERROR_SYSTEM_ERROR,
+ ERROR_INVALID_ARGUMENT,
+ ERROR_DISABLED,
+ ERROR_CANCELLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCode {}
+
+ /**
+ * Error categories.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"ERROR_CATEGORY_"},
+ value = {
+ ERROR_CATEGORY_UNKNOWN,
+ ERROR_CATEGORY_REQUEST_ERROR,
+ ERROR_CATEGORY_APP,
+ ERROR_CATEGORY_SYSTEM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCategory {}
+
+ @NonNull
+ public static final Creator<AppFunctionException> CREATOR =
+ new Creator<>() {
+ @Override
+ public AppFunctionException createFromParcel(Parcel in) {
+ return new AppFunctionException(in);
+ }
+
+ @Override
+ public AppFunctionException[] newArray(int size) {
+ return new AppFunctionException[size];
+ }
+ };
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 5ddb590add4c..ed088fed41c2 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -16,7 +16,7 @@
package android.app.appfunctions;
-import static android.app.appfunctions.ExecuteAppFunctionResponse.getResultCode;
+import static android.app.appfunctions.AppFunctionException.ERROR_SYSTEM_ERROR;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
import android.Manifest;
@@ -39,7 +39,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
/**
* Provides access to app functions.
@@ -147,16 +146,16 @@ public final class AppFunctionManager {
* @param request the request to execute the app function
* @param executor the executor to run the callback
* @param cancellationSignal the cancellation signal to cancel the execution.
- * @param callback the callback to receive the function execution result.
+ * @param callback the callback to receive the function execution result or error.
* <p>If the calling app does not own the app function or does not have {@code
* android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
* android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code
- * ExecuteAppFunctionResponse.RESULT_DENIED}.
+ * AppFunctionException.ERROR_DENIED}.
* <p>If the caller only has {@code android.permission.EXECUTE_APP_FUNCTIONS} but the
* function requires {@code android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}, the execution
- * result will contain {@code ExecuteAppFunctionResponse.RESULT_DENIED}
+ * result will contain {@code AppFunctionException.ERROR_DENIED}
* <p>If the function requested for execution is disabled, then the execution result will
- * contain {@code ExecuteAppFunctionResponse.RESULT_DISABLED}
+ * contain {@code AppFunctionException.ERROR_DISABLED}
* <p>If the cancellation signal is issued, the operation is cancelled and no response is
* returned to the caller.
*/
@@ -171,7 +170,9 @@ public final class AppFunctionManager {
@NonNull ExecuteAppFunctionRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ @NonNull
+ OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+ callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
@@ -186,20 +187,25 @@ public final class AppFunctionManager {
aidlRequest,
new IExecuteAppFunctionCallback.Stub() {
@Override
- public void onResult(ExecuteAppFunctionResponse result) {
+ public void onSuccess(ExecuteAppFunctionResponse result) {
try {
- executor.execute(() -> callback.accept(result));
+ executor.execute(() -> callback.onResult(result));
} catch (RuntimeException e) {
// Ideally shouldn't happen since errors are wrapped into
- // the
- // response, but we catch it here for additional safety.
- callback.accept(
- ExecuteAppFunctionResponse.newFailure(
- getResultCode(e),
- e.getMessage(),
- /* extras= */ null));
+ // the response, but we catch it here for additional safety.
+ executor.execute(
+ () ->
+ callback.onError(
+ new AppFunctionException(
+ ERROR_SYSTEM_ERROR,
+ e.getMessage())));
}
}
+
+ @Override
+ public void onError(AppFunctionException exception) {
+ executor.execute(() -> callback.onError(exception));
+ }
});
if (cancellationTransport != null) {
cancellationSignal.setRemote(cancellationTransport);
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index 06d95f5270c3..3ddda228d145 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -213,9 +213,7 @@ public class AppFunctionRuntimeMetadata extends GenericDocument {
setEnabled(original.getEnabled());
}
- /**
- * Sets an indicator specifying the function enabled state.
- */
+ /** Sets an indicator specifying the function enabled state. */
@NonNull
public Builder setEnabled(@EnabledState int enabledState) {
if (enabledState != APP_FUNCTION_STATE_DEFAULT
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index 63d187aa11ef..85b6ab2b4e61 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -17,7 +17,6 @@
package android.app.appfunctions;
import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
-import static android.app.appfunctions.ExecuteAppFunctionResponse.getResultCode;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -32,10 +31,8 @@ import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.function.Consumer;
/**
* Abstract base class to provide app functions to the system.
@@ -80,7 +77,9 @@ public abstract class AppFunctionService extends Service {
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ @NonNull
+ OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+ callback);
}
/** @hide */
@@ -105,13 +104,22 @@ public abstract class AppFunctionService extends Service {
request,
callingPackage,
buildCancellationSignal(cancellationCallback),
- safeCallback::onResult);
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(ExecuteAppFunctionResponse result) {
+ safeCallback.onResult(result);
+ }
+
+ @Override
+ public void onError(AppFunctionException exception) {
+ safeCallback.onError(exception);
+ }
+ });
} catch (Exception ex) {
// Apps should handle exceptions. But if they don't, report the error on
// behalf of them.
- safeCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- getResultCode(ex), ex.getMessage(), /* extras= */ null));
+ safeCallback.onError(
+ new AppFunctionException(toErrorCode(ex), ex.getMessage()));
}
}
};
@@ -164,12 +172,26 @@ public abstract class AppFunctionService extends Service {
* @param request The function execution request.
* @param callingPackage The package name of the app that is requesting the execution.
* @param cancellationSignal A signal to cancel the execution.
- * @param callback A callback to report back the result.
+ * @param callback A callback to report back the result or error.
*/
@MainThread
public abstract void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ @NonNull
+ OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+ callback);
+
+ /**
+ * Returns result codes from throwable.
+ *
+ * @hide
+ */
+ private static @AppFunctionException.ErrorCode int toErrorCode(@NonNull Throwable t) {
+ if (t instanceof IllegalArgumentException) {
+ return AppFunctionException.ERROR_INVALID_ARGUMENT;
+ }
+ return AppFunctionException.ERROR_APP_UNKNOWN_ERROR;
+ }
}
diff --git a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
index a23f842e6eeb..1869d22ea080 100644
--- a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
@@ -38,7 +38,7 @@ public class AppFunctionStaticMetadataHelper {
public static final String STATIC_SCHEMA_TYPE = "AppFunctionStaticMetadata";
public static final String STATIC_PROPERTY_ENABLED_BY_DEFAULT = "enabledByDefault";
public static final String STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS =
- "restrictCallersWithExecuteAppFunctions";
+ "restrictCallersWithExecuteAppFunctions";
public static final String APP_FUNCTION_STATIC_NAMESPACE = "app_functions";
public static final String PROPERTY_FUNCTION_ID = "functionId";
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index ced4b553d641..f7030265b122 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -19,7 +19,6 @@ package android.app.appfunctions;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.GenericDocument;
@@ -27,8 +26,6 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/** The response to an app function execution. */
@@ -45,10 +42,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
Bundle extras =
Objects.requireNonNull(
parcel.readBundle(Bundle.class.getClassLoader()));
- int resultCode = parcel.readInt();
- String errorMessage = parcel.readString8();
- return new ExecuteAppFunctionResponse(
- resultWrapper, extras, resultCode, errorMessage);
+ return new ExecuteAppFunctionResponse(resultWrapper.getValue(), extras);
}
@Override
@@ -74,112 +68,6 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
/**
- * The call was successful.
- *
- * <p>This result code does not belong in an error category.
- */
- public static final int RESULT_OK = 0;
-
- /**
- * The caller does not have the permission to execute an app function.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_DENIED = 1000;
-
- /**
- * The caller supplied invalid arguments to the execution request.
- *
- * <p>This error may be considered similar to {@link IllegalArgumentException}.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_INVALID_ARGUMENT = 1001;
-
- /**
- * The caller tried to execute a disabled app function.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_DISABLED = 1002;
-
- /**
- * The caller tried to execute a function that does not exist.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_FUNCTION_NOT_FOUND = 1003;
-
- /**
- * An internal unexpected error coming from the system.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
- */
- public static final int RESULT_SYSTEM_ERROR = 2000;
-
- /**
- * The operation was cancelled. Use this error code to report that a cancellation is done after
- * receiving a cancellation signal.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
- */
- public static final int RESULT_CANCELLED = 2001;
-
- /**
- * An unknown error occurred while processing the call in the AppFunctionService.
- *
- * <p>This error is thrown when the service is connected in the remote application but an
- * unexpected error is thrown from the bound application.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
- */
- public static final int RESULT_APP_UNKNOWN_ERROR = 3000;
-
- /**
- * The error category is unknown.
- *
- * <p>This is the default value for {@link #getErrorCategory}.
- */
- public static final int ERROR_CATEGORY_UNKNOWN = 0;
-
- /**
- * The error is caused by the app requesting a function execution.
- *
- * <p>For example, the caller provided invalid parameters in the execution request e.g. an
- * invalid function ID.
- *
- * <p>Errors in the category fall in the range 1000-1999 inclusive.
- */
- public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
-
- /**
- * The error is caused by an issue in the system.
- *
- * <p>For example, the AppFunctionService implementation is not found by the system.
- *
- * <p>Errors in the category fall in the range 2000-2999 inclusive.
- */
- public static final int ERROR_CATEGORY_SYSTEM = 2;
-
- /**
- * The error is caused by the app providing the function.
- *
- * <p>For example, the app crashed when the system is executing the request.
- *
- * <p>Errors in the category fall in the range 3000-3999 inclusive.
- */
- public static final int ERROR_CATEGORY_APP = 3;
-
- /** The result code of the app function execution. */
- @ResultCode private final int mResultCode;
-
- /**
- * The error message associated with the result, if any. This is {@code null} if the result code
- * is {@link #RESULT_OK}.
- */
- @Nullable private final String mErrorMessage;
-
- /**
* Returns the return value of the executed function.
*
* <p>The return value is stored in a {@link GenericDocument} with the key {@link
@@ -192,103 +80,21 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
/** Returns the additional metadata data relevant to this function execution response. */
@NonNull private final Bundle mExtras;
- private ExecuteAppFunctionResponse(
- @NonNull GenericDocumentWrapper resultDocumentWrapper,
- @NonNull Bundle extras,
- @ResultCode int resultCode,
- @Nullable String errorMessage) {
- mResultDocumentWrapper = Objects.requireNonNull(resultDocumentWrapper);
- mExtras = Objects.requireNonNull(extras);
- mResultCode = resultCode;
- mErrorMessage = errorMessage;
- }
-
/**
- * Returns result codes from throwable.
- *
- * @hide
- */
- @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
- static @ResultCode int getResultCode(@NonNull Throwable t) {
- if (t instanceof IllegalArgumentException) {
- return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
- }
- return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
- }
-
- /**
- * Returns a successful response.
- *
* @param resultDocument The return value of the executed function.
- * @param extras The additional metadata for this function execution response.
*/
- @NonNull
- @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
- public static ExecuteAppFunctionResponse newSuccess(
- @NonNull GenericDocument resultDocument, @Nullable Bundle extras) {
- Objects.requireNonNull(resultDocument);
- Bundle actualExtras = getActualExtras(extras);
- GenericDocumentWrapper resultDocumentWrapper = new GenericDocumentWrapper(resultDocument);
-
- return new ExecuteAppFunctionResponse(
- resultDocumentWrapper, actualExtras, RESULT_OK, /* errorMessage= */ null);
+ public ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument) {
+ this(resultDocument, Bundle.EMPTY);
}
/**
- * Returns a failure response.
- *
- * @param resultCode The result code of the app function execution.
+ * @param resultDocument The return value of the executed function.
* @param extras The additional metadata for this function execution response.
- * @param errorMessage The error message associated with the result, if any.
- */
- @NonNull
- @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
- public static ExecuteAppFunctionResponse newFailure(
- @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) {
- if (resultCode == RESULT_OK) {
- throw new IllegalArgumentException("resultCode must not be RESULT_OK");
- }
- Bundle actualExtras = getActualExtras(extras);
- GenericDocumentWrapper emptyWrapper =
- new GenericDocumentWrapper(new GenericDocument.Builder<>("", "", "").build());
- return new ExecuteAppFunctionResponse(emptyWrapper, actualExtras, resultCode, errorMessage);
- }
-
- private static Bundle getActualExtras(@Nullable Bundle extras) {
- if (extras == null) {
- return Bundle.EMPTY;
- }
- return extras;
- }
-
- /**
- * Returns the error category of the {@link ExecuteAppFunctionResponse}.
- *
- * <p>This method categorizes errors based on their underlying cause, allowing developers to
- * implement targeted error handling and provide more informative error messages to users. It
- * maps ranges of result codes to specific error categories.
- *
- * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to
- * ensure correct categorization of the failed response.
- *
- * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to
- * any error category, for example, in the case of a successful result with {@link #RESULT_OK}.
- *
- * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
- * result code ranges.
*/
- @ErrorCategory
- public int getErrorCategory() {
- if (mResultCode >= 1000 && mResultCode < 2000) {
- return ERROR_CATEGORY_REQUEST_ERROR;
- }
- if (mResultCode >= 2000 && mResultCode < 3000) {
- return ERROR_CATEGORY_SYSTEM;
- }
- if (mResultCode >= 3000 && mResultCode < 4000) {
- return ERROR_CATEGORY_APP;
- }
- return ERROR_CATEGORY_UNKNOWN;
+ public ExecuteAppFunctionResponse(
+ @NonNull GenericDocument resultDocument, @NonNull Bundle extras) {
+ mResultDocumentWrapper = new GenericDocumentWrapper(Objects.requireNonNull(resultDocument));
+ mExtras = Objects.requireNonNull(extras);
}
/**
@@ -296,9 +102,6 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
*
* <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>
@@ -324,32 +127,6 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
return mExtras;
}
- /**
- * Returns {@code true} if {@link #getResultCode} equals {@link
- * ExecuteAppFunctionResponse#RESULT_OK}.
- */
- public boolean isSuccess() {
- return getResultCode() == RESULT_OK;
- }
-
- /**
- * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}.
- */
- @ResultCode
- public int getResultCode() {
- return mResultCode;
- }
-
- /**
- * Returns the error message associated with this result.
- *
- * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}.
- */
- @Nullable
- public String getErrorMessage() {
- return mErrorMessage;
- }
-
@Override
public int describeContents() {
return 0;
@@ -359,43 +136,5 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
mResultDocumentWrapper.writeToParcel(dest, flags);
dest.writeBundle(mExtras);
- dest.writeInt(mResultCode);
- dest.writeString8(mErrorMessage);
}
-
- /**
- * Result codes.
- *
- * @hide
- */
- @IntDef(
- prefix = {"RESULT_"},
- value = {
- RESULT_OK,
- RESULT_DENIED,
- RESULT_APP_UNKNOWN_ERROR,
- RESULT_FUNCTION_NOT_FOUND,
- RESULT_SYSTEM_ERROR,
- RESULT_INVALID_ARGUMENT,
- RESULT_DISABLED,
- RESULT_CANCELLED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ResultCode {}
-
- /**
- * Error categories.
- *
- * @hide
- */
- @IntDef(
- prefix = {"ERROR_CATEGORY_"},
- value = {
- ERROR_CATEGORY_UNKNOWN,
- ERROR_CATEGORY_REQUEST_ERROR,
- ERROR_CATEGORY_APP,
- ERROR_CATEGORY_SYSTEM
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ErrorCategory {}
}
diff --git a/core/java/android/app/appfunctions/GenericDocumentWrapper.java b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
index b29b64e44d21..541ca7458efe 100644
--- a/core/java/android/app/appfunctions/GenericDocumentWrapper.java
+++ b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
@@ -34,9 +34,9 @@ import java.util.Objects;
* <p>{#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder
* directly or Android shared memory if the data is large.
*
- * <p>This class performs lazy unparcelling. The `GenericDocument` is only unparcelled
- * from the underlying `Parcel` when {@link #getValue()} is called. This optimization
- * allows the system server to pass through the generic document, without unparcel and parcel it.
+ * <p>This class performs lazy unparcelling. The `GenericDocument` is only unparcelled from the
+ * underlying `Parcel` when {@link #getValue()} is called. This optimization allows the system
+ * server to pass through the generic document, without unparcel and parcel it.
*
* @hide
* @see Parcel#writeBlob(byte[])
@@ -45,8 +45,11 @@ public final class GenericDocumentWrapper implements Parcelable {
@Nullable
@GuardedBy("mLock")
private GenericDocument mGenericDocument;
+
@GuardedBy("mLock")
- @Nullable private Parcel mParcel;
+ @Nullable
+ private Parcel mParcel;
+
private final Object mLock = new Object();
public static final Creator<GenericDocumentWrapper> CREATOR =
diff --git a/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl b/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl
index 5323f9b627e3..69bbc0e5d275 100644
--- a/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl
+++ b/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl
@@ -17,8 +17,10 @@
package android.app.appfunctions;
import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.AppFunctionException;
/** {@hide} */
oneway interface IExecuteAppFunctionCallback {
- void onResult(in ExecuteAppFunctionResponse result);
+ void onSuccess(in ExecuteAppFunctionResponse result);
+ void onError(in AppFunctionException exception);
}
diff --git a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
index 00182447e9a8..2426daf5c9f2 100644
--- a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
+++ b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
@@ -17,17 +17,16 @@
package android.app.appfunctions;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.RemoteException;
import android.util.Log;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
/**
* A wrapper of IExecuteAppFunctionCallback which swallows the {@link RemoteException}. This
- * callback is intended for one-time use only. Subsequent calls to onResult() will be ignored.
+ * callback is intended for one-time use only. Subsequent calls to onResult() or onError() will be
+ * ignored.
*
* @hide
*/
@@ -38,44 +37,41 @@ public class SafeOneTimeExecuteAppFunctionCallback {
@NonNull private final IExecuteAppFunctionCallback mCallback;
- @Nullable private final Consumer<ExecuteAppFunctionResponse> mOnDispatchCallback;
-
public SafeOneTimeExecuteAppFunctionCallback(@NonNull IExecuteAppFunctionCallback callback) {
- this(callback, /* onDispatchCallback= */ null);
- }
-
- /**
- * @param callback The callback to wrap.
- * @param onDispatchCallback An optional callback invoked after the wrapped callback has been
- * dispatched with a result. This callback receives the result that has been dispatched.
- */
- public SafeOneTimeExecuteAppFunctionCallback(
- @NonNull IExecuteAppFunctionCallback callback,
- @Nullable Consumer<ExecuteAppFunctionResponse> onDispatchCallback) {
mCallback = Objects.requireNonNull(callback);
- mOnDispatchCallback = onDispatchCallback;
}
/** Invoke wrapped callback with the result. */
public void onResult(@NonNull ExecuteAppFunctionResponse result) {
if (!mOnResultCalled.compareAndSet(false, true)) {
- Log.w(TAG, "Ignore subsequent calls to onResult()");
+ Log.w(TAG, "Ignore subsequent calls to onResult/onError()");
return;
}
try {
- mCallback.onResult(result);
+ mCallback.onSuccess(result);
} catch (RemoteException ex) {
// Failed to notify the other end. Ignore.
Log.w(TAG, "Failed to invoke the callback", ex);
}
- if (mOnDispatchCallback != null) {
- mOnDispatchCallback.accept(result);
+ }
+
+ /** Invoke wrapped callback with the error. */
+ public void onError(@NonNull AppFunctionException error) {
+ if (!mOnResultCalled.compareAndSet(false, true)) {
+ Log.w(TAG, "Ignore subsequent calls to onResult/onError()");
+ return;
+ }
+ try {
+ mCallback.onError(error);
+ } catch (RemoteException ex) {
+ // Failed to notify the other end. Ignore.
+ Log.w(TAG, "Failed to invoke the callback", ex);
}
}
/**
- * Disables this callback. Subsequent calls to {@link #onResult(ExecuteAppFunctionResponse)}
- * will be ignored.
+ * Disables this callback. Subsequent calls to {@link #onResult(ExecuteAppFunctionResponse)} or
+ * {@link #onError(AppFunctionException)} will be ignored.
*/
public void disable() {
mOnResultCalled.set(true);
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index 0eda10112ee8..f56667df92ca 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -1,9 +1,29 @@
// Signature format: 2.0
package com.android.extensions.appfunctions {
+ public final class AppFunctionException extends java.lang.Exception {
+ ctor public AppFunctionException(int, @Nullable String);
+ ctor public AppFunctionException(int, @Nullable String, @NonNull android.os.Bundle);
+ method public int getErrorCategory();
+ method public int getErrorCode();
+ method @Nullable public String getErrorMessage();
+ method @NonNull public android.os.Bundle getExtras();
+ field public static final int ERROR_APP_UNKNOWN_ERROR = 3000; // 0xbb8
+ field public static final int ERROR_CANCELLED = 2001; // 0x7d1
+ field public static final int ERROR_CATEGORY_APP = 3; // 0x3
+ field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
+ field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
+ field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
+ field public static final int ERROR_DENIED = 1000; // 0x3e8
+ field public static final int ERROR_DISABLED = 1002; // 0x3ea
+ field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb
+ field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9
+ field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0
+ }
+
public final class AppFunctionManager {
ctor public AppFunctionManager(android.content.Context);
- method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.android.extensions.appfunctions.ExecuteAppFunctionResponse>);
+ method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
@@ -15,7 +35,7 @@ package com.android.extensions.appfunctions {
public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.android.extensions.appfunctions.ExecuteAppFunctionResponse>);
+ method @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -35,27 +55,11 @@ package com.android.extensions.appfunctions {
}
public final class ExecuteAppFunctionResponse {
- method public int getErrorCategory();
- method @Nullable public String getErrorMessage();
+ ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument);
+ ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle);
method @NonNull public android.os.Bundle getExtras();
- method public int getResultCode();
method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
- method public boolean isSuccess();
- method @NonNull public static com.android.extensions.appfunctions.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
- method @NonNull public static com.android.extensions.appfunctions.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
- field public static final int ERROR_CATEGORY_APP = 3; // 0x3
- field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
- field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
- field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
- field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8
- field public static final int RESULT_CANCELLED = 2001; // 0x7d1
- field public static final int RESULT_DENIED = 1000; // 0x3e8
- field public static final int RESULT_DISABLED = 1002; // 0x3ea
- field public static final int RESULT_FUNCTION_NOT_FOUND = 1003; // 0x3eb
- field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9
- field public static final int RESULT_OK = 0; // 0x0
- field public static final int RESULT_SYSTEM_ERROR = 2000; // 0x7d0
}
}
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
new file mode 100644
index 000000000000..28c3b3df9b1c
--- /dev/null
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.extensions.appfunctions;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Represents an app function related errors. */
+public final class AppFunctionException extends Exception {
+ /**
+ * The caller does not have the permission to execute an app function.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_DENIED = 1000;
+
+ /**
+ * The caller supplied invalid arguments to the execution request.
+ *
+ * <p>This error may be considered similar to {@link IllegalArgumentException}.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_INVALID_ARGUMENT = 1001;
+
+ /**
+ * The caller tried to execute a disabled app function.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_DISABLED = 1002;
+
+ /**
+ * The caller tried to execute a function that does not exist.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_FUNCTION_NOT_FOUND = 1003;
+
+ /**
+ * An internal unexpected error coming from the system.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+ */
+ public static final int ERROR_SYSTEM_ERROR = 2000;
+
+ /**
+ * The operation was cancelled. Use this error code to report that a cancellation is done after
+ * receiving a cancellation signal.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+ */
+ public static final int ERROR_CANCELLED = 2001;
+
+ /**
+ * An unknown error occurred while processing the call in the AppFunctionService.
+ *
+ * <p>This error is thrown when the service is connected in the remote application but an
+ * unexpected error is thrown from the bound application.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
+ */
+ public static final int ERROR_APP_UNKNOWN_ERROR = 3000;
+
+ /**
+ * The error category is unknown.
+ *
+ * <p>This is the default value for {@link #getErrorCategory}.
+ */
+ public static final int ERROR_CATEGORY_UNKNOWN = 0;
+
+ /**
+ * The error is caused by the app requesting a function execution.
+ *
+ * <p>For example, the caller provided invalid parameters in the execution request e.g. an
+ * invalid function ID.
+ *
+ * <p>Errors in the category fall in the range 1000-1999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
+
+ /**
+ * The error is caused by an issue in the system.
+ *
+ * <p>For example, the AppFunctionService implementation is not found by the system.
+ *
+ * <p>Errors in the category fall in the range 2000-2999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_SYSTEM = 2;
+
+ /**
+ * The error is caused by the app providing the function.
+ *
+ * <p>For example, the app crashed when the system is executing the request.
+ *
+ * <p>Errors in the category fall in the range 3000-3999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_APP = 3;
+
+ private final int mErrorCode;
+ @Nullable private final String mErrorMessage;
+ @NonNull private final Bundle mExtras;
+
+ public AppFunctionException(int errorCode, @Nullable String errorMessage) {
+ this(errorCode, errorMessage, Bundle.EMPTY);
+ }
+
+ public AppFunctionException(
+ int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) {
+ mErrorCode = errorCode;
+ mErrorMessage = errorMessage;
+ mExtras = extras;
+ }
+
+ /** Returns one of the {@code ERROR} constants. */
+ @ErrorCode
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /** Returns the error message. */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /**
+ * Returns the error category.
+ *
+ * <p>This method categorizes errors based on their underlying cause, allowing developers to
+ * implement targeted error handling and provide more informative error messages to users. It
+ * maps ranges of error codes to specific error categories.
+ *
+ * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the error code does not belong to
+ * any error category.
+ *
+ * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
+ * error code ranges.
+ */
+ @ErrorCategory
+ public int getErrorCategory() {
+ if (mErrorCode >= 1000 && mErrorCode < 2000) {
+ return ERROR_CATEGORY_REQUEST_ERROR;
+ }
+ if (mErrorCode >= 2000 && mErrorCode < 3000) {
+ return ERROR_CATEGORY_SYSTEM;
+ }
+ if (mErrorCode >= 3000 && mErrorCode < 4000) {
+ return ERROR_CATEGORY_APP;
+ }
+ return ERROR_CATEGORY_UNKNOWN;
+ }
+
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Error codes.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"ERROR_"},
+ value = {
+ ERROR_DENIED,
+ ERROR_APP_UNKNOWN_ERROR,
+ ERROR_FUNCTION_NOT_FOUND,
+ ERROR_SYSTEM_ERROR,
+ ERROR_INVALID_ARGUMENT,
+ ERROR_DISABLED,
+ ERROR_CANCELLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCode {}
+
+ /**
+ * Error categories.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"ERROR_CATEGORY_"},
+ value = {
+ ERROR_CATEGORY_UNKNOWN,
+ ERROR_CATEGORY_REQUEST_ERROR,
+ ERROR_CATEGORY_APP,
+ ERROR_CATEGORY_SYSTEM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCategory {}
+}
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
index d64593d8ff5f..9eb66a33fedc 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
@@ -31,7 +31,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
/**
* Provides app functions related functionalities.
@@ -115,7 +114,9 @@ public final class AppFunctionManager {
@NonNull ExecuteAppFunctionRequest sidecarRequest,
@NonNull @CallbackExecutor Executor executor,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ @NonNull
+ OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+ callback) {
Objects.requireNonNull(sidecarRequest);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
@@ -126,10 +127,20 @@ public final class AppFunctionManager {
platformRequest,
executor,
cancellationSignal,
- (platformResponse) -> {
- callback.accept(
- SidecarConverter.getSidecarExecuteAppFunctionResponse(
- platformResponse));
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(
+ android.app.appfunctions.ExecuteAppFunctionResponse result) {
+ callback.onResult(
+ SidecarConverter.getSidecarExecuteAppFunctionResponse(result));
+ }
+
+ @Override
+ public void onError(
+ android.app.appfunctions.AppFunctionException exception) {
+ callback.onError(
+ SidecarConverter.getSidecarAppFunctionException(exception));
+ }
});
}
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
index 1a4d9da8bd63..55f579138218 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
@@ -16,7 +16,8 @@
package com.android.extensions.appfunctions;
-import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
+import static com.android.extensions.appfunctions.SidecarConverter.getPlatformAppFunctionException;
+import static com.android.extensions.appfunctions.SidecarConverter.getPlatformExecuteAppFunctionResponse;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -26,8 +27,7 @@ import android.content.Intent;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
-
-import java.util.function.Consumer;
+import android.os.OutcomeReceiver;
/**
* Abstract base class to provide app functions to the system.
@@ -79,10 +79,18 @@ public abstract class AppFunctionService extends Service {
platformRequest),
callingPackage,
cancellationSignal,
- (sidecarResponse) -> {
- callback.accept(
- SidecarConverter.getPlatformExecuteAppFunctionResponse(
- sidecarResponse));
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(ExecuteAppFunctionResponse result) {
+ callback.onResult(
+ getPlatformExecuteAppFunctionResponse(result));
+ }
+
+ @Override
+ public void onError(AppFunctionException exception) {
+ callback.onError(
+ getPlatformAppFunctionException(exception));
+ }
});
});
@@ -115,12 +123,14 @@ public abstract class AppFunctionService extends Service {
* @param request The function execution request.
* @param callingPackage The package name of the app that is requesting the execution.
* @param cancellationSignal A signal to cancel the execution.
- * @param callback A callback to report back the result.
+ * @param callback A callback to report back the result or error.
*/
@MainThread
public abstract void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ @NonNull
+ OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+ callback);
}
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
index 7c5ddcd9edfb..42c3c033ad38 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
@@ -16,23 +16,14 @@
package com.android.extensions.appfunctions;
-import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
+import android.app.appfunctions.AppFunctionManager;
import android.app.appsearch.GenericDocument;
import android.os.Bundle;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
-/**
- * The response to an app function execution.
- *
- * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel
- * functionality and exposes it here as a sidecar library (avoiding direct dependency on the
- * platform API).
- */
+/** The response to an app function execution. */
public final class ExecuteAppFunctionResponse {
/**
* The name of the property that stores the function return value within the {@code
@@ -51,112 +42,6 @@ public final class ExecuteAppFunctionResponse {
public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
/**
- * The call was successful.
- *
- * <p>This result code does not belong in an error category.
- */
- public static final int RESULT_OK = 0;
-
- /**
- * The caller does not have the permission to execute an app function.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_DENIED = 1000;
-
- /**
- * The caller supplied invalid arguments to the execution request.
- *
- * <p>This error may be considered similar to {@link IllegalArgumentException}.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_INVALID_ARGUMENT = 1001;
-
- /**
- * The caller tried to execute a disabled app function.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_DISABLED = 1002;
-
- /**
- * The caller tried to execute a function that does not exist.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_FUNCTION_NOT_FOUND = 1003;
-
- /**
- * An internal unexpected error coming from the system.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
- */
- public static final int RESULT_SYSTEM_ERROR = 2000;
-
- /**
- * The operation was cancelled. Use this error code to report that a cancellation is done after
- * receiving a cancellation signal.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
- */
- public static final int RESULT_CANCELLED = 2001;
-
- /**
- * An unknown error occurred while processing the call in the AppFunctionService.
- *
- * <p>This error is thrown when the service is connected in the remote application but an
- * unexpected error is thrown from the bound application.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
- */
- public static final int RESULT_APP_UNKNOWN_ERROR = 3000;
-
- /**
- * The error category is unknown.
- *
- * <p>This is the default value for {@link #getErrorCategory}.
- */
- public static final int ERROR_CATEGORY_UNKNOWN = 0;
-
- /**
- * The error is caused by the app requesting a function execution.
- *
- * <p>For example, the caller provided invalid parameters in the execution request e.g. an
- * invalid function ID.
- *
- * <p>Errors in the category fall in the range 1000-1999 inclusive.
- */
- public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
-
- /**
- * The error is caused by an issue in the system.
- *
- * <p>For example, the AppFunctionService implementation is not found by the system.
- *
- * <p>Errors in the category fall in the range 2000-2999 inclusive.
- */
- public static final int ERROR_CATEGORY_SYSTEM = 2;
-
- /**
- * The error is caused by the app providing the function.
- *
- * <p>For example, the app crashed when the system is executing the request.
- *
- * <p>Errors in the category fall in the range 3000-3999 inclusive.
- */
- public static final int ERROR_CATEGORY_APP = 3;
-
- /** The result code of the app function execution. */
- @ResultCode private final int mResultCode;
-
- /**
- * The error message associated with the result, if any. This is {@code null} if the result code
- * is {@link #RESULT_OK}.
- */
- @Nullable private final String mErrorMessage;
-
- /**
* Returns the return value of the executed function.
*
* <p>The return value is stored in a {@link GenericDocument} with the key {@link
@@ -169,99 +54,21 @@ public final class ExecuteAppFunctionResponse {
/** Returns the additional metadata data relevant to this function execution response. */
@NonNull private final Bundle mExtras;
- private ExecuteAppFunctionResponse(
- @NonNull GenericDocument resultDocument,
- @NonNull Bundle extras,
- @ResultCode int resultCode,
- @Nullable String errorMessage) {
- mResultDocument = Objects.requireNonNull(resultDocument);
- mExtras = Objects.requireNonNull(extras);
- mResultCode = resultCode;
- mErrorMessage = errorMessage;
- }
-
/**
- * Returns result codes from throwable.
- *
- * @hide
- */
- static @ResultCode int getResultCode(@NonNull Throwable t) {
- if (t instanceof IllegalArgumentException) {
- return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
- }
- return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
- }
-
- /**
- * Returns a successful response.
- *
* @param resultDocument The return value of the executed function.
- * @param extras The additional metadata data relevant to this function execution response.
*/
- @NonNull
- public static ExecuteAppFunctionResponse newSuccess(
- @NonNull GenericDocument resultDocument, @Nullable Bundle extras) {
- Objects.requireNonNull(resultDocument);
- Bundle actualExtras = getActualExtras(extras);
-
- return new ExecuteAppFunctionResponse(
- resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null);
+ public ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument) {
+ this(resultDocument, Bundle.EMPTY);
}
/**
- * Returns a failure response.
- *
- * @param resultCode The result code of the app function execution.
- * @param extras The additional metadata data relevant to this function execution response.
- * @param errorMessage The error message associated with the result, if any.
- */
- @NonNull
- public static ExecuteAppFunctionResponse newFailure(
- @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) {
- if (resultCode == RESULT_OK) {
- throw new IllegalArgumentException("resultCode must not be RESULT_OK");
- }
- Bundle actualExtras = getActualExtras(extras);
- GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build();
- return new ExecuteAppFunctionResponse(
- emptyDocument, actualExtras, resultCode, errorMessage);
- }
-
- private static Bundle getActualExtras(@Nullable Bundle extras) {
- if (extras == null) {
- return Bundle.EMPTY;
- }
- return extras;
- }
-
- /**
- * Returns the error category of the {@link ExecuteAppFunctionResponse}.
- *
- * <p>This method categorizes errors based on their underlying cause, allowing developers to
- * implement targeted error handling and provide more informative error messages to users. It
- * maps ranges of result codes to specific error categories.
- *
- * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to
- * ensure correct categorization of the failed response.
- *
- * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to
- * any error category, for example, in the case of a successful result with {@link #RESULT_OK}.
- *
- * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
- * result code ranges.
+ * @param resultDocument The return value of the executed function.
+ * @param extras The additional metadata for this function execution response.
*/
- @ErrorCategory
- public int getErrorCategory() {
- if (mResultCode >= 1000 && mResultCode < 2000) {
- return ERROR_CATEGORY_REQUEST_ERROR;
- }
- if (mResultCode >= 2000 && mResultCode < 3000) {
- return ERROR_CATEGORY_SYSTEM;
- }
- if (mResultCode >= 3000 && mResultCode < 4000) {
- return ERROR_CATEGORY_APP;
- }
- return ERROR_CATEGORY_UNKNOWN;
+ public ExecuteAppFunctionResponse(
+ @NonNull GenericDocument resultDocument, @NonNull Bundle extras) {
+ mResultDocument = Objects.requireNonNull(resultDocument);
+ mExtras = Objects.requireNonNull(extras);
}
/**
@@ -269,9 +76,6 @@ public final class ExecuteAppFunctionResponse {
*
* <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
*
- * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed
- * function does not produce a return value.
- *
* <p>Sample code for extracting the return value:
*
* <pre>
@@ -283,77 +87,17 @@ public final class ExecuteAppFunctionResponse {
* // Do something with the returnValue
* }
* </pre>
+ *
+ * @see AppFunctionManager on how to determine the expected function return.
*/
@NonNull
public GenericDocument getResultDocument() {
return mResultDocument;
}
- /** Returns the extras of the app function execution. */
+ /** Returns the additional metadata for this function execution response. */
@NonNull
public Bundle getExtras() {
return mExtras;
}
-
- /**
- * Returns {@code true} if {@link #getResultCode} equals {@link
- * ExecuteAppFunctionResponse#RESULT_OK}.
- */
- public boolean isSuccess() {
- return getResultCode() == RESULT_OK;
- }
-
- /**
- * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}.
- */
- @ResultCode
- public int getResultCode() {
- return mResultCode;
- }
-
- /**
- * Returns the error message associated with this result.
- *
- * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}.
- */
- @Nullable
- public String getErrorMessage() {
- return mErrorMessage;
- }
-
- /**
- * Result codes.
- *
- * @hide
- */
- @IntDef(
- prefix = {"RESULT_"},
- value = {
- RESULT_OK,
- RESULT_DENIED,
- RESULT_APP_UNKNOWN_ERROR,
- RESULT_SYSTEM_ERROR,
- RESULT_FUNCTION_NOT_FOUND,
- RESULT_INVALID_ARGUMENT,
- RESULT_DISABLED,
- RESULT_CANCELLED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ResultCode {}
-
- /**
- * Error categories.
- *
- * @hide
- */
- @IntDef(
- prefix = {"ERROR_CATEGORY_"},
- value = {
- ERROR_CATEGORY_UNKNOWN,
- ERROR_CATEGORY_REQUEST_ERROR,
- ERROR_CATEGORY_APP,
- ERROR_CATEGORY_SYSTEM
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ErrorCategory {}
}
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java
index 56f2725fccc7..5e1fc7e684e2 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java
@@ -52,13 +52,21 @@ public final class SidecarConverter {
@NonNull
public static android.app.appfunctions.ExecuteAppFunctionResponse
getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) {
- if (response.isSuccess()) {
- return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess(
- response.getResultDocument(), response.getExtras());
- } else {
- return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure(
- response.getResultCode(), response.getErrorMessage(), response.getExtras());
- }
+ return new android.app.appfunctions.ExecuteAppFunctionResponse(
+ response.getResultDocument(), response.getExtras());
+ }
+
+ /**
+ * Converts sidecar's {@link AppFunctionException} into platform's {@link
+ * android.app.appfunctions.AppFunctionException}
+ *
+ * @hide
+ */
+ @NonNull
+ public static android.app.appfunctions.AppFunctionException
+ getPlatformAppFunctionException(@NonNull AppFunctionException exception) {
+ return new android.app.appfunctions.AppFunctionException(
+ exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras());
}
/**
@@ -86,12 +94,19 @@ public final class SidecarConverter {
@NonNull
public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse(
@NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) {
- if (response.isSuccess()) {
- return ExecuteAppFunctionResponse.newSuccess(
- response.getResultDocument(), response.getExtras());
- } else {
- return ExecuteAppFunctionResponse.newFailure(
- response.getResultCode(), response.getErrorMessage(), response.getExtras());
- }
+ return new ExecuteAppFunctionResponse(response.getResultDocument(), response.getExtras());
+ }
+
+ /**
+ * Converts platform's {@link android.app.appfunctions.AppFunctionException} into
+ * sidecar's {@link AppFunctionException}
+ *
+ * @hide
+ */
+ @NonNull
+ public static AppFunctionException getSidecarAppFunctionException(
+ @NonNull android.app.appfunctions.AppFunctionException exception) {
+ return new AppFunctionException(
+ exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras());
}
}
diff --git a/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt
index 6118e6ce4ab2..11202d58e484 100644
--- a/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt
+++ b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt
@@ -16,6 +16,7 @@
package com.android.extensions.appfunctions.tests
+import android.app.appfunctions.AppFunctionException
import android.app.appfunctions.ExecuteAppFunctionRequest
import android.app.appfunctions.ExecuteAppFunctionResponse
import android.app.appsearch.GenericDocument
@@ -83,44 +84,38 @@ class SidecarConverterTest {
GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
.setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true)
.build()
- val platformResponse = ExecuteAppFunctionResponse.newSuccess(resultGd, null)
+ val platformResponse = ExecuteAppFunctionResponse(resultGd)
val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse(
platformResponse
)
- assertThat(sidecarResponse.isSuccess).isTrue()
assertThat(
sidecarResponse.resultDocument.getProperty(
ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE
)
)
.isEqualTo(booleanArrayOf(true))
- assertThat(sidecarResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK)
- assertThat(sidecarResponse.errorMessage).isNull()
}
@Test
- fun getSidecarExecuteAppFunctionResponse_errorResponse_sameContents() {
- val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
- val platformResponse =
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
- null,
- null
+ fun getSidecarAppFunctionException_sameContents() {
+ val bundle = Bundle()
+ bundle.putString("key", "value")
+ val platformException =
+ AppFunctionException(
+ AppFunctionException.ERROR_SYSTEM_ERROR,
+ "error",
+ bundle
)
- val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse(
- platformResponse
+ val sidecarException = SidecarConverter.getSidecarAppFunctionException(
+ platformException
)
- assertThat(sidecarResponse.isSuccess).isFalse()
- assertThat(sidecarResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace)
- assertThat(sidecarResponse.resultDocument.id).isEqualTo(emptyGd.id)
- assertThat(sidecarResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
- assertThat(sidecarResponse.resultCode)
- .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
- assertThat(sidecarResponse.errorMessage).isNull()
+ assertThat(sidecarException.errorCode).isEqualTo(AppFunctionException.ERROR_SYSTEM_ERROR)
+ assertThat(sidecarException.errorMessage).isEqualTo("error")
+ assertThat(sidecarException.extras.getString("key")).isEqualTo("value")
}
@Test
@@ -130,46 +125,38 @@ class SidecarConverterTest {
.setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true)
.build()
val sidecarResponse =
- com.android.extensions.appfunctions.ExecuteAppFunctionResponse.newSuccess(
- resultGd,
- null
- )
+ com.android.extensions.appfunctions.ExecuteAppFunctionResponse(resultGd)
val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse(
sidecarResponse
)
- assertThat(platformResponse.isSuccess).isTrue()
assertThat(
platformResponse.resultDocument.getProperty(
ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE
)
)
.isEqualTo(booleanArrayOf(true))
- assertThat(platformResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK)
- assertThat(platformResponse.errorMessage).isNull()
}
@Test
- fun getPlatformExecuteAppFunctionResponse_errorResponse_sameContents() {
- val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
- val sidecarResponse =
- com.android.extensions.appfunctions.ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
- null,
- null
+ fun getPlatformAppFunctionException_sameContents() {
+ val bundle = Bundle()
+ bundle.putString("key", "value")
+ val sidecarException =
+ com.android.extensions.appfunctions.AppFunctionException(
+ AppFunctionException.ERROR_SYSTEM_ERROR,
+ "error",
+ bundle
)
- val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse(
- sidecarResponse
+ val platformException = SidecarConverter.getPlatformAppFunctionException(
+ sidecarException
)
- assertThat(platformResponse.isSuccess).isFalse()
- assertThat(platformResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace)
- assertThat(platformResponse.resultDocument.id).isEqualTo(emptyGd.id)
- assertThat(platformResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
- assertThat(platformResponse.resultCode)
- .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
- assertThat(platformResponse.errorMessage).isNull()
+ assertThat(platformException.errorCode)
+ .isEqualTo(AppFunctionException.ERROR_SYSTEM_ERROR)
+ assertThat(platformException.errorMessage).isEqualTo("error")
+ assertThat(platformException.extras.getString("key")).isEqualTo("value")
}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 268e56487c4b..f13e22950e2d 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -29,7 +29,7 @@ import android.app.appfunctions.AppFunctionManagerHelper;
import android.app.appfunctions.AppFunctionRuntimeMetadata;
import android.app.appfunctions.AppFunctionStaticMetadataHelper;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
-import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.AppFunctionException;
import android.app.appfunctions.IAppFunctionEnabledCallback;
import android.app.appfunctions.IAppFunctionManager;
import android.app.appfunctions.IAppFunctionService;
@@ -156,11 +156,10 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
mCallerValidator.verifyTargetUserHandle(
requestInternal.getUserHandle(), validatedCallingPackage);
} catch (SecurityException exception) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_DENIED,
- exception.getMessage(),
- /* extras= */ null));
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_DENIED,
+ exception.getMessage()));
return null;
}
@@ -180,7 +179,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
safeExecuteAppFunctionCallback,
executeAppFunctionCallback.asBinder());
} catch (Exception e) {
- safeExecuteAppFunctionCallback.onResult(
+ safeExecuteAppFunctionCallback.onError(
mapExceptionToExecuteAppFunctionResponse(e));
}
});
@@ -198,22 +197,19 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
UserHandle targetUser = requestInternal.getUserHandle();
// TODO(b/354956319): Add and honor the new enterprise policies.
if (mCallerValidator.isUserOrganizationManaged(targetUser)) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR,
"Cannot run on a device with a device owner or from the managed"
- + " profile.",
- /* extras= */ null));
+ + " profile."));
return;
}
String targetPackageName = requestInternal.getClientRequest().getTargetPackageName();
if (TextUtils.isEmpty(targetPackageName)) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT,
- "Target package name cannot be empty.",
- /* extras= */ null));
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_INVALID_ARGUMENT,
+ "Target package name cannot be empty."));
return;
}
@@ -253,11 +249,10 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
mInternalServiceHelper.resolveAppFunctionService(
targetPackageName, targetUser);
if (serviceIntent == null) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
- "Cannot find the target service.",
- /* extras= */ null));
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_SYSTEM_ERROR,
+ "Cannot find the target service."));
return;
}
bindAppFunctionServiceUnchecked(
@@ -272,7 +267,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
})
.exceptionally(
ex -> {
- safeExecuteAppFunctionCallback.onResult(
+ safeExecuteAppFunctionCallback.onError(
mapExceptionToExecuteAppFunctionResponse(ex));
return null;
});
@@ -446,11 +441,9 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
if (!bindServiceResult) {
Slog.e(TAG, "Failed to bind to the AppFunctionService");
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
- "Failed to bind the AppFunctionService.",
- /* extras= */ null));
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR,
+ "Failed to bind the AppFunctionService."));
}
}
@@ -459,22 +452,21 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
.getSystemService(AppSearchManager.class);
}
- private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) {
+ private AppFunctionException mapExceptionToExecuteAppFunctionResponse(Throwable e) {
if (e instanceof CompletionException) {
e = e.getCause();
}
- int resultCode = ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR;
+ int resultCode = AppFunctionException.ERROR_SYSTEM_ERROR;
if (e instanceof AppSearchException appSearchException) {
resultCode =
mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(
appSearchException.getResultCode());
} else if (e instanceof SecurityException) {
- resultCode = ExecuteAppFunctionResponse.RESULT_DENIED;
+ resultCode = AppFunctionException.ERROR_DENIED;
} else if (e instanceof DisabledAppFunctionException) {
- resultCode = ExecuteAppFunctionResponse.RESULT_DISABLED;
+ resultCode = AppFunctionException.ERROR_DISABLED;
}
- return ExecuteAppFunctionResponse.newFailure(
- resultCode, e.getMessage(), /* extras= */ null);
+ return new AppFunctionException(resultCode, e.getMessage());
}
private int mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(int resultCode) {
@@ -485,13 +477,13 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
switch (resultCode) {
case AppSearchResult.RESULT_NOT_FOUND:
- return ExecuteAppFunctionResponse.RESULT_FUNCTION_NOT_FOUND;
+ return AppFunctionException.ERROR_FUNCTION_NOT_FOUND;
case AppSearchResult.RESULT_INVALID_ARGUMENT:
case AppSearchResult.RESULT_INTERNAL_ERROR:
case AppSearchResult.RESULT_SECURITY_ERROR:
// fall-through
}
- return ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR;
+ return AppFunctionException.ERROR_SYSTEM_ERROR;
}
private void registerAppSearchObserver(@NonNull TargetUser user) {
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
index 129be65f3153..c689bb92f8f7 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
@@ -17,6 +17,7 @@ package com.android.server.appfunctions;
import android.annotation.NonNull;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.AppFunctionException;
import android.app.appfunctions.ExecuteAppFunctionResponse;
import android.app.appfunctions.IAppFunctionService;
import android.app.appfunctions.ICancellationCallback;
@@ -57,17 +58,22 @@ public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAp
mCancellationCallback,
new IExecuteAppFunctionCallback.Stub() {
@Override
- public void onResult(ExecuteAppFunctionResponse response) {
+ public void onSuccess(ExecuteAppFunctionResponse response) {
mSafeExecuteAppFunctionCallback.onResult(response);
serviceUsageCompleteListener.onCompleted();
}
+
+ @Override
+ public void onError(AppFunctionException error) {
+ mSafeExecuteAppFunctionCallback.onError(error);
+ serviceUsageCompleteListener.onCompleted();
+ }
});
} catch (Exception e) {
- mSafeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
- e.getMessage(),
- /* extras= */ null));
+ mSafeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_APP_UNKNOWN_ERROR,
+ e.getMessage()));
serviceUsageCompleteListener.onCompleted();
}
}
@@ -75,11 +81,9 @@ public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAp
@Override
public void onFailedToConnect() {
Slog.e(TAG, "Failed to connect to service");
- mSafeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
- "Failed to connect to AppFunctionService",
- /* extras= */ null));
+ mSafeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(AppFunctionException.ERROR_APP_UNKNOWN_ERROR,
+ "Failed to connect to AppFunctionService"));
}
@Override