• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app.appfunctions;
18 
19 import static android.app.appfunctions.AppFunctionException.ERROR_SYSTEM_ERROR;
20 import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
21 
22 import android.Manifest;
23 import android.annotation.CallbackExecutor;
24 import android.annotation.FlaggedApi;
25 import android.annotation.IntDef;
26 import android.annotation.NonNull;
27 import android.annotation.RequiresPermission;
28 import android.annotation.SystemService;
29 import android.annotation.UserHandleAware;
30 import android.app.appfunctions.AppFunctionManagerHelper.AppFunctionNotFoundException;
31 import android.app.appsearch.AppSearchManager;
32 import android.content.Context;
33 import android.os.CancellationSignal;
34 import android.os.ICancellationSignal;
35 import android.os.OutcomeReceiver;
36 import android.os.ParcelableException;
37 import android.os.RemoteException;
38 import android.os.SystemClock;
39 
40 import java.lang.annotation.Retention;
41 import java.lang.annotation.RetentionPolicy;
42 import java.util.Objects;
43 import java.util.concurrent.Executor;
44 
45 /**
46  * Provides access to App Functions. App Functions is currently a
47  * beta/experimental preview feature.
48  *
49  * <p>An app function is a piece of functionality that apps expose to the system for cross-app
50  * orchestration.
51  *
52  * <h3>Building App Functions</h3>
53  *
54  * <p>Most developers should build app functions through the AppFunctions SDK. This SDK library
55  * offers a more convenient and type-safe way to build app functions. The SDK provides predefined
56  * function schemas for common use cases and associated data classes for function parameters and
57  * return values. Apps only have to implement the provided interfaces. Internally, the SDK converts
58  * these data classes into {@link ExecuteAppFunctionRequest#getParameters()} and {@link
59  * ExecuteAppFunctionResponse#getResultDocument()}.
60  *
61  * <h3>Discovering App Functions</h3>
62  *
63  * <p>When there is a package change or the device starts up, the metadata of available functions is
64  * indexed on-device by {@link AppSearchManager}. AppSearch stores the indexed information as an
65  * {@code AppFunctionStaticMetadata} document. This document contains the {@code functionIdentifier}
66  * and the schema information that the app function implements. This allows other apps and the app
67  * itself to discover these functions using the AppSearch search APIs. Visibility to this metadata
68  * document is based on the packages that have visibility to the app providing the app functions.
69  * AppFunction SDK provides a convenient way to achieve this and is the preferred method.
70  *
71  * <h3>Executing App Functions</h3>
72  *
73  * <p>To execute an app function, the caller app can retrieve the {@code functionIdentifier} from
74  * the {@code AppFunctionStaticMetadata} document and use it to build an {@link
75  * ExecuteAppFunctionRequest}. Then, invoke {@link #executeAppFunction} with the request to execute
76  * the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS} permission to
77  * execute app functions from other apps. An app can always execute its own app functions and
78  * doesn't need these permissions. AppFunction SDK provides a convenient way to achieve this and
79  * is the preferred method.
80  *
81  * <h3>Example</h3>
82  *
83  * <p>An assistant app is trying to fulfill the user request "Save XYZ into my note". The assistant
84  * app should first list all available app functions as {@code AppFunctionStaticMetadata} documents
85  * from AppSearch. Then, it should identify an app function that implements the {@code CreateNote}
86  * schema. Finally, the assistant app can invoke {@link #executeAppFunction} with the {@code
87  * functionIdentifier} of the chosen function.
88  */
89 @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
90 @SystemService(Context.APP_FUNCTION_SERVICE)
91 public final class AppFunctionManager {
92 
93     /**
94      * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset
95      * enabled state to the default value.
96      */
97     public static final int APP_FUNCTION_STATE_DEFAULT = 0;
98 
99     /**
100      * The app function is enabled. To enable an app function, call {@link #setAppFunctionEnabled}
101      * with this value.
102      */
103     public static final int APP_FUNCTION_STATE_ENABLED = 1;
104 
105     /**
106      * The app function is disabled. To disable an app function, call {@link #setAppFunctionEnabled}
107      * with this value.
108      */
109     public static final int APP_FUNCTION_STATE_DISABLED = 2;
110 
111     private final IAppFunctionManager mService;
112     private final Context mContext;
113 
114     /**
115      * The enabled state of the app function.
116      *
117      * @hide
118      */
119     @IntDef(
120             prefix = {"APP_FUNCTION_STATE_"},
121             value = {
122                 APP_FUNCTION_STATE_DEFAULT,
123                 APP_FUNCTION_STATE_ENABLED,
124                 APP_FUNCTION_STATE_DISABLED
125             })
126     @Retention(RetentionPolicy.SOURCE)
127     public @interface EnabledState {}
128 
129     /**
130      * Creates an instance.
131      *
132      * @param service An interface to the backing service.
133      * @param context A {@link Context}.
134      * @hide
135      */
AppFunctionManager(IAppFunctionManager service, Context context)136     public AppFunctionManager(IAppFunctionManager service, Context context) {
137         mService = service;
138         mContext = context;
139     }
140 
141     /**
142      * Executes the app function.
143      *
144      * <p>Note: Applications can execute functions they define. To execute functions defined in
145      * another component, apps would need to have the permission
146      * {@code android.permission.EXECUTE_APP_FUNCTIONS}.
147      *
148      * @param request the request to execute the app function
149      * @param executor the executor to run the callback
150      * @param cancellationSignal the cancellation signal to cancel the execution.
151      * @param callback the callback to receive the function execution result or error.
152      *     <p>If the calling app does not own the app function or does not have {@code
153      *     android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code
154      *     AppFunctionException.ERROR_DENIED}.
155      *     <p>If the caller only has {@code android.permission.EXECUTE_APP_FUNCTIONS}, the execution
156      *     result will contain {@code AppFunctionException.ERROR_DENIED}
157      *     <p>If the function requested for execution is disabled, then the execution result will
158      *     contain {@code AppFunctionException.ERROR_DISABLED}
159      *     <p>If the cancellation signal is issued, the operation is cancelled and no response is
160      *     returned to the caller.
161      */
162     @RequiresPermission(value = Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional = true)
163     @UserHandleAware
executeAppFunction( @onNull ExecuteAppFunctionRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback)164     public void executeAppFunction(
165             @NonNull ExecuteAppFunctionRequest request,
166             @NonNull @CallbackExecutor Executor executor,
167             @NonNull CancellationSignal cancellationSignal,
168             @NonNull
169                     OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
170                             callback) {
171         Objects.requireNonNull(request);
172         Objects.requireNonNull(executor);
173         Objects.requireNonNull(callback);
174 
175         ExecuteAppFunctionAidlRequest aidlRequest =
176                 new ExecuteAppFunctionAidlRequest(
177                         request, mContext.getUser(), mContext.getPackageName(),
178                         /* requestTime= */ SystemClock.elapsedRealtime());
179 
180         try {
181             ICancellationSignal cancellationTransport =
182                     mService.executeAppFunction(
183                             aidlRequest,
184                             new IExecuteAppFunctionCallback.Stub() {
185                                 @Override
186                                 public void onSuccess(ExecuteAppFunctionResponse result) {
187                                     try {
188                                         executor.execute(() -> callback.onResult(result));
189                                     } catch (RuntimeException e) {
190                                         // Ideally shouldn't happen since errors are wrapped into
191                                         // the response, but we catch it here for additional safety.
192                                         executor.execute(
193                                                 () ->
194                                                         callback.onError(
195                                                                 new AppFunctionException(
196                                                                         ERROR_SYSTEM_ERROR,
197                                                                         e.getMessage())));
198                                     }
199                                 }
200 
201                                 @Override
202                                 public void onError(AppFunctionException exception) {
203                                     executor.execute(() -> callback.onError(exception));
204                                 }
205                             });
206             if (cancellationTransport != null) {
207                 cancellationSignal.setRemote(cancellationTransport);
208             }
209         } catch (RemoteException e) {
210             throw e.rethrowFromSystemServer();
211         }
212     }
213 
214     /**
215      * Returns a boolean through a callback, indicating whether the app function is enabled.
216      *
217      * <p>This method can only check app functions owned by the caller, or those where the caller
218      * has visibility to the owner package and holds the
219      * {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} permission.
220      *
221      * <p>If the operation fails, the callback's {@link OutcomeReceiver#onError} is called with
222      * errors:
223      *
224      * <ul>
225      *   <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
226      *       have access to it.
227      * </ul>
228      *
229      * @param functionIdentifier the identifier of the app function to check (unique within the
230      *     target package) and in most cases, these are automatically generated by the AppFunctions
231      *     SDK
232      * @param targetPackage the package name of the app function's owner
233      * @param executor the executor to run the request
234      * @param callback the callback to receive the function enabled check result
235      */
236     @RequiresPermission(value = Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional = true)
isAppFunctionEnabled( @onNull String functionIdentifier, @NonNull String targetPackage, @NonNull Executor executor, @NonNull OutcomeReceiver<Boolean, Exception> callback)237     public void isAppFunctionEnabled(
238             @NonNull String functionIdentifier,
239             @NonNull String targetPackage,
240             @NonNull Executor executor,
241             @NonNull OutcomeReceiver<Boolean, Exception> callback) {
242         isAppFunctionEnabledInternal(functionIdentifier, targetPackage, executor, callback);
243     }
244 
245     /**
246      * Returns a boolean through a callback, indicating whether the app function is enabled.
247      *
248      * <p>This method can only check app functions owned by the caller, unlike {@link
249      * #isAppFunctionEnabled(String, String, Executor, OutcomeReceiver)}, which allows specifying a
250      * different target package.
251      *
252      * <p>If the operation fails, the callback's {@link OutcomeReceiver#onError} is called with
253      * errors:
254      *
255      * <ul>
256      *   <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
257      *       have access to it.
258      * </ul>
259      *
260      * @param functionIdentifier the identifier of the app function to check (unique within the
261      *     target package) and in most cases, these are automatically generated by the AppFunctions
262      *     SDK
263      * @param executor the executor to run the request
264      * @param callback the callback to receive the function enabled check result
265      */
isAppFunctionEnabled( @onNull String functionIdentifier, @NonNull Executor executor, @NonNull OutcomeReceiver<Boolean, Exception> callback)266     public void isAppFunctionEnabled(
267             @NonNull String functionIdentifier,
268             @NonNull Executor executor,
269             @NonNull OutcomeReceiver<Boolean, Exception> callback) {
270         isAppFunctionEnabledInternal(
271                 functionIdentifier, mContext.getPackageName(), executor, callback);
272     }
273 
274     /**
275      * Sets the enabled state of the app function owned by the calling package.
276      *
277      * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
278      *
279      * <ul>
280      *   <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
281      *       have access to it.
282      * </ul>
283      *
284      * @param functionIdentifier the identifier of the app function to enable (unique within the
285      *     calling package). In most cases, identifiers are automatically generated by the
286      *     AppFunctions SDK
287      * @param newEnabledState the new state of the app function
288      * @param executor the executor to run the callback
289      * @param callback the callback to receive the result of the function enablement. The call was
290      *     successful if no exception was thrown.
291      */
292     @UserHandleAware
setAppFunctionEnabled( @onNull String functionIdentifier, @EnabledState int newEnabledState, @NonNull Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback)293     public void setAppFunctionEnabled(
294             @NonNull String functionIdentifier,
295             @EnabledState int newEnabledState,
296             @NonNull Executor executor,
297             @NonNull OutcomeReceiver<Void, Exception> callback) {
298         Objects.requireNonNull(functionIdentifier);
299         Objects.requireNonNull(executor);
300         Objects.requireNonNull(callback);
301         CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback);
302         try {
303             mService.setAppFunctionEnabled(
304                     mContext.getPackageName(),
305                     functionIdentifier,
306                     mContext.getUser(),
307                     newEnabledState,
308                     callbackWrapper);
309         } catch (RemoteException e) {
310             throw e.rethrowFromSystemServer();
311         }
312     }
313 
isAppFunctionEnabledInternal( @onNull String functionIdentifier, @NonNull String targetPackage, @NonNull Executor executor, @NonNull OutcomeReceiver<Boolean, Exception> callback)314     private void isAppFunctionEnabledInternal(
315             @NonNull String functionIdentifier,
316             @NonNull String targetPackage,
317             @NonNull Executor executor,
318             @NonNull OutcomeReceiver<Boolean, Exception> callback) {
319         Objects.requireNonNull(functionIdentifier);
320         Objects.requireNonNull(targetPackage);
321         Objects.requireNonNull(executor);
322         Objects.requireNonNull(callback);
323         AppSearchManager appSearchManager = mContext.getSystemService(AppSearchManager.class);
324         if (appSearchManager == null) {
325             callback.onError(new IllegalStateException("Failed to get AppSearchManager."));
326             return;
327         }
328 
329         // Wrap the callback to convert AppFunctionNotFoundException to IllegalArgumentException
330         // to match the documentation.
331         OutcomeReceiver<Boolean, Exception> callbackWithExceptionInterceptor =
332                 new OutcomeReceiver<>() {
333                     @Override
334                     public void onResult(@NonNull Boolean result) {
335                         callback.onResult(result);
336                     }
337 
338                     @Override
339                     public void onError(@NonNull Exception exception) {
340                         if (exception instanceof AppFunctionNotFoundException) {
341                             exception = new IllegalArgumentException(exception);
342                         }
343                         callback.onError(exception);
344                     }
345                 };
346 
347         AppFunctionManagerHelper.isAppFunctionEnabled(
348                 functionIdentifier, targetPackage, appSearchManager, executor,
349                 callbackWithExceptionInterceptor);
350 
351     }
352 
353     private static class CallbackWrapper extends IAppFunctionEnabledCallback.Stub {
354 
355         private final OutcomeReceiver<Void, Exception> mCallback;
356         private final Executor mExecutor;
357 
CallbackWrapper( @onNull Executor callbackExecutor, @NonNull OutcomeReceiver<Void, Exception> callback)358         CallbackWrapper(
359                 @NonNull Executor callbackExecutor,
360                 @NonNull OutcomeReceiver<Void, Exception> callback) {
361             mCallback = callback;
362             mExecutor = callbackExecutor;
363         }
364 
365         @Override
onSuccess()366         public void onSuccess() {
367             mExecutor.execute(() -> mCallback.onResult(null));
368         }
369 
370         @Override
onError(@onNull ParcelableException exception)371         public void onError(@NonNull ParcelableException exception) {
372             mExecutor.execute(
373                     () -> {
374                         if (IllegalArgumentException.class.isAssignableFrom(
375                                 exception.getCause().getClass())) {
376                             mCallback.onError((IllegalArgumentException) exception.getCause());
377                         } else if (SecurityException.class.isAssignableFrom(
378                                 exception.getCause().getClass())) {
379                             mCallback.onError((SecurityException) exception.getCause());
380                         } else {
381                             mCallback.onError(exception);
382                         }
383                     });
384         }
385     }
386 }
387