Merge changes from topic "appfunction-afm" into main
* changes:
Fix error codes
Fix implementing AFMS.executeAppFunction
Add AppFunction Execution API [AppFunctionManager#executeAppFunction]
diff --git a/core/api/current.txt b/core/api/current.txt
index c06b814..f96e4fd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8722,6 +8722,7 @@
package android.app.appfunctions {
@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 java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
}
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 7801201..4b6f406 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -16,11 +16,22 @@
package android.app.appfunctions;
+import static android.app.appfunctions.ExecuteAppFunctionResponse.getResultCode;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemService;
+import android.annotation.UserHandleAware;
import android.content.Context;
+import android.os.RemoteException;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Provides app functions related functionalities.
@@ -28,6 +39,7 @@
* <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.
*/
+// TODO(b/357551503): Implement get and set enabled app function APIs.
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
@SystemService(Context.APP_FUNCTION_SERVICE)
public final class AppFunctionManager {
@@ -35,7 +47,10 @@
private final Context mContext;
/**
- * TODO(b/357551503): add comments when implement this class
+ * Creates an instance.
+ *
+ * @param mService An interface to the backing service.
+ * @param context A {@link Context}.
*
* @hide
*/
@@ -43,4 +58,63 @@
this.mService = mService;
this.mContext = context;
}
+
+ /**
+ * Executes the app function.
+ * <p>
+ * Note: Applications can execute functions they define. To execute functions defined in
+ * another component, apps would need to have
+ * {@code android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or
+ * {@code android.permission.EXECUTE_APP_FUNCTIONS}.
+ *
+ * @param request the request to execute the app function
+ * @param executor the executor to run the callback
+ * @param callback the callback to receive the function execution result. 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}.
+ */
+ // TODO(b/360864791): Document that apps can opt-out from being executed by callers with
+ // EXECUTE_APP_FUNCTIONS and how a caller knows whether a function is opted out.
+ // TODO(b/357551503): Update documentation when get / set APIs are implemented that this will
+ // also return RESULT_DENIED if the app function is disabled.
+ @RequiresPermission(
+ anyOf = {Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
+ Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional = true)
+ @UserHandleAware
+ public void executeAppFunction(
+ @NonNull ExecuteAppFunctionRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback
+ ) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ ExecuteAppFunctionAidlRequest aidlRequest =
+ new ExecuteAppFunctionAidlRequest(
+ request,
+ mContext.getUser(),
+ mContext.getPackageName());
+ try {
+ mService.executeAppFunction(
+ aidlRequest,
+ new IExecuteAppFunctionCallback.Stub() {
+ @Override
+ public void onResult(ExecuteAppFunctionResponse result) {
+ try {
+ executor.execute(() -> callback.accept(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(new ExecuteAppFunctionResponse.Builder(
+ getResultCode(e), e.getMessage()).build());
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index fca465f..6259d16 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -16,9 +16,10 @@
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;
-import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
import android.annotation.FlaggedApi;
import android.annotation.MainThread;
@@ -81,18 +82,11 @@
// behalf of them.
safeCallback.onResult(
new ExecuteAppFunctionResponse.Builder(
- getResultCode(ex), ex.getMessage()).build());
+ getResultCode(ex), getExceptionMessage(ex)).build());
}
}
};
- private static int getResultCode(@NonNull Throwable t) {
- if (t instanceof IllegalArgumentException) {
- return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
- }
- return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
- }
-
@NonNull
@Override
public final IBinder onBind(@Nullable Intent intent) {
@@ -116,11 +110,15 @@
* 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 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);
+
+ private String getExceptionMessage(Exception exception) {
+ return exception.getMessage() == null ? "" : exception.getMessage();
+ }
}
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index 72205d9..872327d 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -236,6 +236,18 @@
}
/**
+ * 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;
+ }
+
+ /**
* The builder for creating {@link ExecuteAppFunctionResponse} instances.
*/
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
diff --git a/core/java/android/app/appfunctions/IAppFunctionManager.aidl b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
index 14944f0..ef37095 100644
--- a/core/java/android/app/appfunctions/IAppFunctionManager.aidl
+++ b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
@@ -20,10 +20,11 @@
import android.app.appfunctions.IExecuteAppFunctionCallback;
/**
-* Interface between an app and the server implementation service (AppFunctionManagerService).
-* @hide
-*/
-oneway interface IAppFunctionManager {
+ * Defines the interface for apps to interact with the app function execution service
+ * {@code AppFunctionManagerService} running in the system server process.
+ * @hide
+ */
+interface IAppFunctionManager {
/**
* Executes an app function provided by {@link AppFunctionService} through the system.
*
diff --git a/core/java/android/app/appfunctions/IAppFunctionService.aidl b/core/java/android/app/appfunctions/IAppFunctionService.aidl
index 12b5c55..cc5a20c 100644
--- a/core/java/android/app/appfunctions/IAppFunctionService.aidl
+++ b/core/java/android/app/appfunctions/IAppFunctionService.aidl
@@ -21,7 +21,14 @@
import android.app.appfunctions.ExecuteAppFunctionRequest;
- /** {@hide} */
+/**
+ * Defines the interface for the system server to request the execution of an app function within
+ * the app process.
+ *
+ * This interface is implemented by the app and exposed to the system server via a {@code Service}.
+ *
+ * @hide
+ */
oneway interface IAppFunctionService {
/**
* Called by the system to execute a specific app function.
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index e2167a8..191ec69 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -82,10 +82,19 @@
final SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback =
new SafeOneTimeExecuteAppFunctionCallback(executeAppFunctionCallback);
- String validatedCallingPackage = mCallerValidator
- .validateCallingPackage(requestInternal.getCallingPackage());
- UserHandle targetUser = mCallerValidator.verifyTargetUserHandle(
- requestInternal.getUserHandle(), validatedCallingPackage);
+ String validatedCallingPackage;
+ UserHandle targetUser;
+ try {
+ validatedCallingPackage = mCallerValidator
+ .validateCallingPackage(requestInternal.getCallingPackage());
+ targetUser = mCallerValidator.verifyTargetUserHandle(
+ requestInternal.getUserHandle(), validatedCallingPackage);
+ } catch (SecurityException exception) {
+ safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
+ .Builder(ExecuteAppFunctionResponse.RESULT_DENIED,
+ getExceptionMessage(exception)).build());
+ return;
+ }
// TODO(b/354956319): Add and honor the new enterprise policies.
if (mCallerValidator.isUserOrganizationManaged(targetUser)) {
@@ -107,8 +116,11 @@
if (!mCallerValidator.verifyCallerCanExecuteAppFunction(
validatedCallingPackage, targetPackageName)) {
- throw new SecurityException("Caller does not have permission to execute the app "
- + "function.");
+ safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
+ .Builder(ExecuteAppFunctionResponse.RESULT_DENIED,
+ "Caller does not have permission to execute the appfunction")
+ .build());
+ return;
}
Intent serviceIntent = mInternalServiceHelper.resolveAppFunctionService(
@@ -159,8 +171,8 @@
);
} catch (Exception e) {
safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
- .Builder(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
- e.getMessage()).build());
+ .Builder(ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
+ getExceptionMessage(e)).build());
serviceUsageCompleteListener.onCompleted();
}
}
@@ -169,7 +181,7 @@
public void onFailedToConnect() {
Slog.e(TAG, "Failed to connect to service");
safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
- .Builder(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+ .Builder(ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
"Failed to connect to AppFunctionService").build());
}
@@ -193,4 +205,8 @@
).build());
}
}
+
+ private String getExceptionMessage(Exception exception) {
+ return exception.getMessage() == null ? "" : exception.getMessage();
+ }
}