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();
+    }
 }