diff options
7 files changed, 188 insertions, 27 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 8eb881139b34..fb8fe488f51e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8732,13 +8732,15 @@ 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>); + method @Deprecated @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>); + 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 android.os.CancellationSignal, @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 { 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>); + method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); + method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java index 4682f3d30e1e..216ba5d994ec 100644 --- a/core/java/android/app/appfunctions/AppFunctionManager.java +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -27,6 +27,8 @@ import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.UserHandleAware; import android.content.Context; +import android.os.CancellationSignal; +import android.os.ICancellationSignal; import android.os.RemoteException; import java.util.Objects; @@ -73,7 +75,43 @@ public final class AppFunctionManager { * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code * android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code * ExecuteAppFunctionResponse.RESULT_DENIED}. + * @deprecated Use {@link #executeAppFunction(ExecuteAppFunctionRequest, Executor, + * CancellationSignal, Consumer)} instead. This method will be removed once usage references + * are updated. */ + @RequiresPermission( + anyOf = { + Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, + Manifest.permission.EXECUTE_APP_FUNCTIONS + }, + conditional = true) + @UserHandleAware + @Deprecated + public void executeAppFunction( + @NonNull ExecuteAppFunctionRequest request, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<ExecuteAppFunctionResponse> callback) { + executeAppFunction(request, executor, new CancellationSignal(), callback); + } + + /** + * 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 cancellationSignal the cancellation signal to cancel the execution. + * @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/357551503): Document the behavior when the cancellation signal is issued. // 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 @@ -88,6 +126,7 @@ public final class AppFunctionManager { public void executeAppFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull @CallbackExecutor Executor executor, + @NonNull CancellationSignal cancellationSignal, @NonNull Consumer<ExecuteAppFunctionResponse> callback) { Objects.requireNonNull(request); Objects.requireNonNull(executor); @@ -96,25 +135,31 @@ public final class AppFunctionManager { 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( - ExecuteAppFunctionResponse.newFailure( - getResultCode(e), - e.getMessage(), - /* extras= */ null)); - } - } - }); + ICancellationSignal cancellationTransport = + 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( + ExecuteAppFunctionResponse.newFailure( + getResultCode(e), + e.getMessage(), + /* extras= */ null)); + } + } + }); + if (cancellationTransport != null) { + cancellationSignal.setRemote(cancellationTransport); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java index 0d981ea5a679..8e417737515e 100644 --- a/core/java/android/app/appfunctions/AppFunctionService.java +++ b/core/java/android/app/appfunctions/AppFunctionService.java @@ -29,7 +29,12 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; +import android.os.ICancellationSignal; +import android.os.CancellationSignal; +import android.os.RemoteCallback; +import android.os.RemoteException; import java.util.function.Consumer; @@ -74,6 +79,7 @@ public abstract class AppFunctionService extends Service { */ void perform( @NonNull ExecuteAppFunctionRequest request, + @NonNull CancellationSignal cancellationSignal, @NonNull Consumer<ExecuteAppFunctionResponse> callback); } @@ -85,6 +91,7 @@ public abstract class AppFunctionService extends Service { @Override public void executeAppFunction( @NonNull ExecuteAppFunctionRequest request, + @NonNull ICancellationCallback cancellationCallback, @NonNull IExecuteAppFunctionCallback callback) { if (context.checkCallingPermission(BIND_APP_FUNCTION_SERVICE) == PERMISSION_DENIED) { @@ -93,7 +100,10 @@ public abstract class AppFunctionService extends Service { SafeOneTimeExecuteAppFunctionCallback safeCallback = new SafeOneTimeExecuteAppFunctionCallback(callback); try { - onExecuteFunction.perform(request, safeCallback::onResult); + onExecuteFunction.perform( + request, + buildCancellationSignal(cancellationCallback), + safeCallback::onResult); } catch (Exception ex) { // Apps should handle exceptions. But if they don't, report the error on // behalf of them. @@ -105,6 +115,21 @@ public abstract class AppFunctionService extends Service { }; } + private static CancellationSignal buildCancellationSignal( + @NonNull ICancellationCallback cancellationCallback) { + final ICancellationSignal cancellationSignalTransport = + CancellationSignal.createTransport(); + CancellationSignal cancellationSignal = + CancellationSignal.fromTransport(cancellationSignalTransport); + try { + cancellationCallback.sendCancellationTransport(cancellationSignalTransport); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + return cancellationSignal ; + } + private final Binder mBinder = createBinder( AppFunctionService.this, AppFunctionService.this::onExecuteFunction); @@ -115,6 +140,7 @@ public abstract class AppFunctionService extends Service { return mBinder; } + /** * Called by the system to execute a specific app function. * @@ -134,9 +160,45 @@ public abstract class AppFunctionService extends Service { * * @param request The function execution request. * @param callback A callback to report back the result. + * + * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal, + * Consumer)} instead. This method will be removed once usage references are updated. */ @MainThread + @Deprecated public abstract void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull Consumer<ExecuteAppFunctionResponse> callback); + + /** + * 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. + * + * <p>This method also accepts a {@link CancellationSignal} that the app should listen to cancel + * the execution of function if requested by the system. + * + * @param request The function execution request. + * @param cancellationSignal A signal to cancel the execution. + * @param callback A callback to report back the result. + */ + @MainThread + public void onExecuteFunction( + @NonNull ExecuteAppFunctionRequest request, + @NonNull CancellationSignal cancellationSignal, + @NonNull Consumer<ExecuteAppFunctionResponse> callback) { + onExecuteFunction(request, callback); + } } diff --git a/core/java/android/app/appfunctions/IAppFunctionManager.aidl b/core/java/android/app/appfunctions/IAppFunctionManager.aidl index 28827bb3052c..c63217ffe850 100644 --- a/core/java/android/app/appfunctions/IAppFunctionManager.aidl +++ b/core/java/android/app/appfunctions/IAppFunctionManager.aidl @@ -18,6 +18,7 @@ package android.app.appfunctions; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; import android.app.appfunctions.IExecuteAppFunctionCallback; +import android.os.ICancellationSignal; /** * Defines the interface for apps to interact with the app function execution service @@ -32,8 +33,8 @@ interface IAppFunctionManager { * @param callback the callback to report the result. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional = true)") - void executeAppFunction( + ICancellationSignal executeAppFunction( in ExecuteAppFunctionAidlRequest request, in IExecuteAppFunctionCallback callback ); -}
\ No newline at end of file +} diff --git a/core/java/android/app/appfunctions/IAppFunctionService.aidl b/core/java/android/app/appfunctions/IAppFunctionService.aidl index cc5a20cfa194..291f33ccb1b8 100644 --- a/core/java/android/app/appfunctions/IAppFunctionService.aidl +++ b/core/java/android/app/appfunctions/IAppFunctionService.aidl @@ -16,7 +16,7 @@ package android.app.appfunctions; -import android.os.Bundle; +import android.app.appfunctions.ICancellationCallback; import android.app.appfunctions.IExecuteAppFunctionCallback; import android.app.appfunctions.ExecuteAppFunctionRequest; @@ -34,10 +34,12 @@ oneway interface IAppFunctionService { * Called by the system to execute a specific app function. * * @param request the function execution request. + * @param cancellationCallback a callback to send back the cancellation transport. * @param callback a callback to report back the result. */ void executeAppFunction( in ExecuteAppFunctionRequest request, + in ICancellationCallback cancellationCallback, in IExecuteAppFunctionCallback callback ); } diff --git a/core/java/android/app/appfunctions/ICancellationCallback.aidl b/core/java/android/app/appfunctions/ICancellationCallback.aidl new file mode 100644 index 000000000000..03235aca017a --- /dev/null +++ b/core/java/android/app/appfunctions/ICancellationCallback.aidl @@ -0,0 +1,24 @@ +/* + * 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.ICancellationSignal; + +/** {@hide} */ +oneway interface ICancellationCallback { + void sendCancellationTransport(in ICancellationSignal cancellationTransport); +}
\ No newline at end of file diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index c8f8c2a6b223..082459b8c863 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -21,11 +21,13 @@ import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_E import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.WorkerThread; +import android.app.appfunctions.AppFunctionService; import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; import android.app.appfunctions.ExecuteAppFunctionResponse; import android.app.appfunctions.IAppFunctionManager; import android.app.appfunctions.IAppFunctionService; +import android.app.appfunctions.ICancellationCallback; import android.app.appfunctions.IExecuteAppFunctionCallback; import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback; import android.app.appsearch.AppSearchManager; @@ -37,11 +39,15 @@ import android.app.appsearch.observer.SchemaChangeInfo; import android.content.Context; import android.content.Intent; import android.os.Binder; +import android.os.CancellationSignal; +import android.os.IBinder; +import android.os.ICancellationSignal; import android.os.UserHandle; import android.text.TextUtils; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.infra.AndroidFuture; import com.android.server.SystemService.TargetUser; import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback; import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener; @@ -99,7 +105,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } @Override - public void executeAppFunction( + public ICancellationSignal executeAppFunction( @NonNull ExecuteAppFunctionAidlRequest requestInternal, @NonNull IExecuteAppFunctionCallback executeAppFunctionCallback) { Objects.requireNonNull(requestInternal); @@ -120,11 +126,14 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { ExecuteAppFunctionResponse.RESULT_DENIED, exception.getMessage(), /* extras= */ null)); - return; + return null; } int callingUid = Binder.getCallingUid(); - int callingPid = Binder.getCallingUid(); + int callingPid = Binder.getCallingPid(); + + ICancellationSignal localCancelTransport = CancellationSignal.createTransport(); + THREAD_POOL_EXECUTOR.execute( () -> { try { @@ -132,12 +141,14 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { requestInternal, callingUid, callingPid, + localCancelTransport, safeExecuteAppFunctionCallback); } catch (Exception e) { safeExecuteAppFunctionCallback.onResult( mapExceptionToExecuteAppFunctionResponse(e)); } }); + return localCancelTransport; } @WorkerThread @@ -145,6 +156,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { ExecuteAppFunctionAidlRequest requestInternal, int callingUid, int callingPid, + ICancellationSignal localCancelTransport, SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) { UserHandle targetUser = requestInternal.getUserHandle(); // TODO(b/354956319): Add and honor the new enterprise policies. @@ -203,6 +215,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { requestInternal, serviceIntent, targetUser, + localCancelTransport, safeExecuteAppFunctionCallback, /* bindFlags= */ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE); @@ -219,8 +232,19 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { @NonNull ExecuteAppFunctionAidlRequest requestInternal, @NonNull Intent serviceIntent, @NonNull UserHandle targetUser, + @NonNull ICancellationSignal cancellationSignalTransport, @NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback, int bindFlags) { + CancellationSignal cancellationSignal = + CancellationSignal.fromTransport(cancellationSignalTransport); + ICancellationCallback cancellationCallback = + new ICancellationCallback.Stub() { + @Override + public void sendCancellationTransport( + @NonNull ICancellationSignal cancellationTransport) { + cancellationSignal.setRemote(cancellationTransport); + } + }; boolean bindServiceResult = mRemoteServiceCaller.runServiceCall( serviceIntent, @@ -236,6 +260,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { try { service.executeAppFunction( requestInternal.getClientRequest(), + cancellationCallback, new IExecuteAppFunctionCallback.Stub() { @Override public void onResult( |