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