• 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.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
20 import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
21 import static android.content.pm.PackageManager.PERMISSION_DENIED;
22 
23 import android.annotation.FlaggedApi;
24 import android.annotation.MainThread;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.annotation.SdkConstant;
28 import android.app.Service;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.pm.SigningInfo;
32 import android.os.Binder;
33 import android.os.CancellationSignal;
34 import android.os.IBinder;
35 import android.os.ICancellationSignal;
36 import android.os.OutcomeReceiver;
37 import android.os.RemoteException;
38 
39 /**
40  * Abstract base class to provide app functions to the system.
41  *
42  * <p>Include the following in the manifest:
43  *
44  * <pre>
45  * {@literal
46  * <service android:name=".YourService"
47  *       android:permission="android.permission.BIND_APP_FUNCTION_SERVICE">
48  *    <intent-filter>
49  *      <action android:name="android.app.appfunctions.AppFunctionService" />
50  *    </intent-filter>
51  * </service>
52  * }
53  * </pre>
54  *
55  * @see AppFunctionManager
56  */
57 @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
58 public abstract class AppFunctionService extends Service {
59     /**
60      * The {@link Intent} that must be declared as handled by the service. To be supported, the
61      * service must also require the {@link BIND_APP_FUNCTION_SERVICE} permission so that other
62      * applications can not abuse it.
63      */
64     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
65     @NonNull
66     public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
67 
68     /**
69      * Functional interface to represent the execution logic of an app function.
70      *
71      * @hide
72      */
73     @FunctionalInterface
74     public interface OnExecuteFunction {
75         /**
76          * Performs the semantic of executing the function specified by the provided request and
77          * return the response through the provided callback.
78          */
perform( @onNull ExecuteAppFunctionRequest request, @NonNull String callingPackage, @NonNull SigningInfo callingPackageSigningInfo, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback)79         void perform(
80                 @NonNull ExecuteAppFunctionRequest request,
81                 @NonNull String callingPackage,
82                 @NonNull SigningInfo callingPackageSigningInfo,
83                 @NonNull CancellationSignal cancellationSignal,
84                 @NonNull
85                         OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback);
86     }
87 
88     /** @hide */
89     @NonNull
createBinder( @onNull Context context, @NonNull OnExecuteFunction onExecuteFunction)90     public static Binder createBinder(
91             @NonNull Context context, @NonNull OnExecuteFunction onExecuteFunction) {
92         return new IAppFunctionService.Stub() {
93             @Override
94             public void executeAppFunction(
95                     @NonNull ExecuteAppFunctionRequest request,
96                     @NonNull String callingPackage,
97                     @NonNull SigningInfo callingPackageSigningInfo,
98                     @NonNull ICancellationCallback cancellationCallback,
99                     @NonNull IExecuteAppFunctionCallback callback) {
100                 if (context.checkCallingPermission(BIND_APP_FUNCTION_SERVICE)
101                         == PERMISSION_DENIED) {
102                     throw new SecurityException("Can only be called by the system server.");
103                 }
104                 SafeOneTimeExecuteAppFunctionCallback safeCallback =
105                         new SafeOneTimeExecuteAppFunctionCallback(callback);
106                 try {
107                     onExecuteFunction.perform(
108                             request,
109                             callingPackage,
110                             callingPackageSigningInfo,
111                             buildCancellationSignal(cancellationCallback),
112                             new OutcomeReceiver<>() {
113                                 @Override
114                                 public void onResult(ExecuteAppFunctionResponse result) {
115                                     safeCallback.onResult(result);
116                                 }
117 
118                                 @Override
119                                 public void onError(AppFunctionException exception) {
120                                     safeCallback.onError(exception);
121                                 }
122                             });
123                 } catch (Exception ex) {
124                     // Apps should handle exceptions. But if they don't, report the error on
125                     // behalf of them.
126                     safeCallback.onError(
127                             new AppFunctionException(toErrorCode(ex), ex.getMessage()));
128                 }
129             }
130         };
131     }
132 
133     private static CancellationSignal buildCancellationSignal(
134             @NonNull ICancellationCallback cancellationCallback) {
135         final ICancellationSignal cancellationSignalTransport =
136                 CancellationSignal.createTransport();
137         CancellationSignal cancellationSignal =
138                 CancellationSignal.fromTransport(cancellationSignalTransport);
139         try {
140             cancellationCallback.sendCancellationTransport(cancellationSignalTransport);
141         } catch (RemoteException e) {
142             throw e.rethrowFromSystemServer();
143         }
144 
145         return cancellationSignal;
146     }
147 
148     private final Binder mBinder =
149             createBinder(AppFunctionService.this, AppFunctionService.this::onExecuteFunction);
150 
151     @NonNull
152     @Override
153     public final IBinder onBind(@Nullable Intent intent) {
154         return mBinder;
155     }
156 
157     /**
158      * Called by the system to execute a specific app function.
159      *
160      * <p>This method is the entry point for handling all app function requests in an app. When the
161      * system needs your AppFunctionService to perform a function, it will invoke this method.
162      *
163      * <p>Each function you've registered is identified by a unique identifier. This identifier
164      * doesn't need to be globally unique, but it must be unique within your app. For example, a
165      * function to order food could be identified as "orderFood". In most cases, this identifier is
166      * automatically generated by the AppFunctions SDK.
167      *
168      * <p>You can determine which function to execute by calling {@link
169      * ExecuteAppFunctionRequest#getFunctionIdentifier()}. This allows your service to route the
170      * incoming request to the appropriate logic for handling the specific function.
171      *
172      * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
173      * thread and dispatch the result with the given callback. You should always report back the
174      * result using the callback, no matter if the execution was successful or not.
175      *
176      * <p>This method also accepts a {@link CancellationSignal} that the app should listen to cancel
177      * the execution of function if requested by the system.
178      *
179      * @param request The function execution request.
180      * @param callingPackage The package name of the app that is requesting the execution.
181      * @param callingPackageSigningInfo The signing information of the app that is requesting the
182      *     execution.
183      * @param cancellationSignal A signal to cancel the execution.
184      * @param callback A callback to report back the result or error.
185      */
186     @MainThread
187     public abstract void onExecuteFunction(
188             @NonNull ExecuteAppFunctionRequest request,
189             @NonNull String callingPackage,
190             @NonNull SigningInfo callingPackageSigningInfo,
191             @NonNull CancellationSignal cancellationSignal,
192             @NonNull OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback);
193 
194     /**
195      * Returns result codes from throwable.
196      *
197      * @hide
198      */
199     private static @AppFunctionException.ErrorCode int toErrorCode(@NonNull Throwable t) {
200         if (t instanceof IllegalArgumentException) {
201             return AppFunctionException.ERROR_INVALID_ARGUMENT;
202         }
203         return AppFunctionException.ERROR_APP_UNKNOWN_ERROR;
204     }
205 }
206