Add AppFunctionService.

- Ported from implementation in AppSearch.
- Simplified not using wrapper on top of ExecutionAppResponse.

Bug: 359983784
Flag: android.app.appfunctions.flags.enable_app_function_manager
API-Coverage-Bug: 357551503
Test: none (deferred with adding the CTS for executeAppFunction)
Change-Id: Ic8b53585269be1be7738a654511dcb25211074fb
diff --git a/core/api/current.txt b/core/api/current.txt
index 66a1d30..c06b814 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8724,6 +8724,13 @@
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
   }
 
+  @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 java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+    field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
+  }
+
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionRequest implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.os.Bundle getExtras();
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
new file mode 100644
index 0000000..fca465f
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -0,0 +1,126 @@
+/*
+ * 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 static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+import java.util.function.Consumer;
+
+/**
+ * Abstract base class to provide app functions to the system.
+ *
+ * <p>Include the following in the manifest:
+ *
+ * <pre>
+ * {@literal
+ * <service android:name=".YourService"
+ *       android:permission="android.permission.BIND_APP_FUNCTION_SERVICE">
+ *    <intent-filter>
+ *      <action android:name="android.app.appfunctions.AppFunctionService" />
+ *    </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ *
+ * @see AppFunctionManager
+ */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public abstract class AppFunctionService extends Service {
+    /**
+     * The {@link Intent} that must be declared as handled by the service. To be supported, the
+     * service must also require the {@link BIND_APP_FUNCTION_SERVICE} permission so that other
+     * applications can not abuse it.
+     */
+    @NonNull
+    public static final String SERVICE_INTERFACE =
+            "android.app.appfunctions.AppFunctionService";
+
+    private final Binder mBinder =
+            new IAppFunctionService.Stub() {
+                @Override
+                public void executeAppFunction(
+                        @NonNull ExecuteAppFunctionRequest request,
+                        @NonNull IExecuteAppFunctionCallback callback) {
+                    if (AppFunctionService.this.checkCallingPermission(
+                            BIND_APP_FUNCTION_SERVICE) == PERMISSION_DENIED) {
+                        throw new SecurityException("Can only be called by the system server.");
+                    }
+                    SafeOneTimeExecuteAppFunctionCallback safeCallback =
+                            new SafeOneTimeExecuteAppFunctionCallback(callback);
+                    try {
+                        AppFunctionService.this.onExecuteFunction(
+                                request,
+                                safeCallback::onResult);
+                    } catch (Exception ex) {
+                        // Apps should handle exceptions. But if they don't, report the error on
+                        // behalf of them.
+                        safeCallback.onResult(
+                                new ExecuteAppFunctionResponse.Builder(
+                                        getResultCode(ex), ex.getMessage()).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) {
+        return mBinder;
+    }
+
+    /**
+     * Called by the system to execute a specific app function.
+     *
+     * <p>This method is triggered when the system requests your AppFunctionService to handle a
+     * particular function you have registered and made available.
+     *
+     * <p>To ensure proper routing of function requests, assign a unique identifier to each
+     * function. This identifier doesn't need to be globally unique, but it must be unique within
+     * your app. For example, a function to order food could be identified as "orderFood". In most
+     * cases this identifier should come from the ID automatically generated by the AppFunctions
+     * SDK. You can determine the specific function to invoke by calling {@link
+     * ExecuteAppFunctionRequest#getFunctionIdentifier()}.
+     *
+     * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
+     * thread and dispatch the result with the given callback. You should always report back the
+     * result using the callback, no matter if the execution was successful or not.
+     *
+     * @param request The function execution request.
+     * @param callback A callback to report back the result.
+     */
+    @MainThread
+    public abstract void onExecuteFunction(
+            @NonNull ExecuteAppFunctionRequest request,
+            @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+}
diff --git a/core/java/android/app/appfunctions/IAppFunctionService.aidl b/core/java/android/app/appfunctions/IAppFunctionService.aidl
new file mode 100644
index 0000000..12b5c55
--- /dev/null
+++ b/core/java/android/app/appfunctions/IAppFunctionService.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.os.Bundle;
+import android.app.appfunctions.IExecuteAppFunctionCallback;
+import android.app.appfunctions.ExecuteAppFunctionRequest;
+
+
+ /** {@hide} */
+oneway interface IAppFunctionService {
+    /**
+     * Called by the system to execute a specific app function.
+     *
+     * @param request  the function execution request.
+     * @param callback a callback to report back the result.
+     */
+    void executeAppFunction(
+        in ExecuteAppFunctionRequest request,
+        in IExecuteAppFunctionCallback callback
+    );
+}
diff --git a/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl b/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl
new file mode 100644
index 0000000..5323f9b
--- /dev/null
+++ b/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright 2020, 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.ExecuteAppFunctionResponse;
+
+/** {@hide} */
+oneway interface IExecuteAppFunctionCallback {
+    void onResult(in ExecuteAppFunctionResponse result);
+}
diff --git a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
new file mode 100644
index 0000000..86fc369
--- /dev/null
+++ b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
@@ -0,0 +1,75 @@
+/*
+ * 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.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.
+ *
+ * @hide
+ */
+public class SafeOneTimeExecuteAppFunctionCallback {
+    private static final String TAG = "SafeOneTimeExecuteApp";
+
+    private final AtomicBoolean mOnResultCalled = new AtomicBoolean(false);
+
+    @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()");
+            return;
+        }
+        try {
+            mCallback.onResult(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);
+        }
+    }
+}