summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> 2024-08-21 08:27:57 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-08-21 08:27:57 +0000
commit422a2ca6f762242ef62981a7cd69f9990a86db66 (patch)
treeeff0170b0afd8c6cae6410626c6e023b3ea046a7
parent57ff494cc5b363b67fa26b354eab8f19b9a8d1ad (diff)
parent4ff28cab089b5b3d5209f214524b90b35bd44258 (diff)
Merge changes from topic "appfunction-afm" into main
* changes: Fix error codes Fix implementing AFMS.executeAppFunction Add AppFunction Execution API [AppFunctionManager#executeAppFunction]
-rw-r--r--core/api/current.txt1
-rw-r--r--core/java/android/app/appfunctions/AppFunctionManager.java76
-rw-r--r--core/java/android/app/appfunctions/AppFunctionService.java18
-rw-r--r--core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java12
-rw-r--r--core/java/android/app/appfunctions/IAppFunctionManager.aidl9
-rw-r--r--core/java/android/app/appfunctions/IAppFunctionService.aidl9
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java34
7 files changed, 134 insertions, 25 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index c06b81498bc9..f96e4fdbeeb9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8722,6 +8722,7 @@ package android.app.admin {
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 7801201e8bcb..4b6f406ded9d 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 @@ import android.content.Context;
* <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 @@ public final class AppFunctionManager {
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 @@ public final class AppFunctionManager {
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 fca465f5e8a9..6259d165d132 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 @@ public abstract class AppFunctionService extends Service {
// 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 @@ public abstract class AppFunctionService extends Service {
* 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 72205d9cf6c6..872327ddd461 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -236,6 +236,18 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
}
/**
+ * 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 14944f043d74..ef37095fbfa4 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.ExecuteAppFunctionAidlRequest;
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 12b5c55c7441..cc5a20cfa194 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.IExecuteAppFunctionCallback;
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 e2167a889cff..191ec69b25c8 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 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
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 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
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 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
);
} 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 class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
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 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
).build());
}
}
+
+ private String getExceptionMessage(Exception exception) {
+ return exception.getMessage() == null ? "" : exception.getMessage();
+ }
}