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