1 /*
2  * Copyright (C) 2022 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.adservices.ondevicepersonalization;
18 
19 import android.adservices.ondevicepersonalization.aidl.IExecuteCallback;
20 import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationManagingService;
21 import android.adservices.ondevicepersonalization.aidl.IRequestSurfacePackageCallback;
22 import android.annotation.CallbackExecutor;
23 import android.annotation.FlaggedApi;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.pm.PackageManager;
29 import android.os.Binder;
30 import android.os.Bundle;
31 import android.os.IBinder;
32 import android.os.OutcomeReceiver;
33 import android.os.PersistableBundle;
34 import android.os.SystemClock;
35 import android.view.SurfaceControlViewHost;
36 
37 import com.android.adservices.ondevicepersonalization.flags.Flags;
38 import com.android.federatedcompute.internal.util.AbstractServiceBinder;
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.modules.utils.build.SdkLevel;
41 import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice;
42 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
43 import com.android.ondevicepersonalization.internal.util.PersistableBundleUtils;
44 
45 import java.util.List;
46 import java.util.Objects;
47 import java.util.concurrent.Executor;
48 
49 // TODO(b/289102463): Add a link to the public ODP developer documentation.
50 /**
51  * OnDevicePersonalizationManager provides APIs for apps to load an
52  * {@link IsolatedService} in an isolated process and interact with it.
53  *
54  * An app can request an {@link IsolatedService} to generate content for display
55  * within an {@link android.view.SurfaceView} within the app's view hierarchy, and also write
56  * persistent results to on-device storage which can be consumed by Federated Analytics for
57  * cross-device statistical analysis or by Federated Learning for model training. The displayed
58  * content and the persistent output are both not directly accessible by the calling app.
59  */
60 @FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
61 public class OnDevicePersonalizationManager {
62     /** @hide */
63     public static final String ON_DEVICE_PERSONALIZATION_SERVICE =
64             "on_device_personalization_service";
65     private static final String INTENT_FILTER_ACTION = "android.OnDevicePersonalizationService";
66     private static final String ODP_MANAGING_SERVICE_PACKAGE_SUFFIX =
67             "com.android.ondevicepersonalization.services";
68 
69     private static final String ALT_ODP_MANAGING_SERVICE_PACKAGE_SUFFIX =
70             "com.google.android.ondevicepersonalization.services";
71 
72     private static final String ODP_INTERNAL_ERROR_MESSAGE =
73             "Internal error in the OnDevicePersonalizationService.";
74 
75     private static final String ISOLATED_SERVICE_ERROR_MESSAGE = "Error in the IsolatedService.";
76 
77     private static final String ODP_DISABLED_ERROR_MESSAGE =
78             "Personalization disabled by device configuration.";
79 
80     private static final String TAG = OnDevicePersonalizationManager.class.getSimpleName();
81     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
82     private final AbstractServiceBinder<IOnDevicePersonalizationManagingService> mServiceBinder;
83     private final Context mContext;
84 
85     /**
86      * The result of a call to {@link OnDevicePersonalizationManager#execute(ComponentName,
87      * PersistableBundle, Executor, OutcomeReceiver)}
88      */
89     public static class ExecuteResult {
90         @Nullable private final SurfacePackageToken mSurfacePackageToken;
91         @Nullable private final byte[] mOutputData;
92 
93         /** @hide */
ExecuteResult( @ullable SurfacePackageToken surfacePackageToken, @Nullable byte[] outputData)94         ExecuteResult(
95                 @Nullable SurfacePackageToken surfacePackageToken,
96                 @Nullable byte[] outputData) {
97             mSurfacePackageToken = surfacePackageToken;
98             mOutputData = outputData;
99         }
100 
101         /**
102          * Returns a {@link SurfacePackageToken}, which is an opaque reference to content that
103          * can be displayed in a {@link android.view.SurfaceView}. This may be null if the
104          * {@link IsolatedService} has not generated any content to be displayed within the
105          * calling app.
106          */
getSurfacePackageToken()107         @Nullable public SurfacePackageToken getSurfacePackageToken() {
108             return mSurfacePackageToken;
109         }
110 
111         /**
112          * Returns the output data that was returned by the {@link IsolatedService}. This will be
113          * non-null if the {@link IsolatedService} returns any results to the caller, and the
114          * egress of data from the {@link IsolatedService} to the specific calling app is allowed
115          * by policy as well as an allowlist.
116          */
getOutputData()117         @Nullable public byte[] getOutputData() {
118             return mOutputData;
119         }
120     }
121 
122     /** @hide */
OnDevicePersonalizationManager(Context context)123     public OnDevicePersonalizationManager(Context context) {
124         this(
125                 context,
126                 AbstractServiceBinder.getServiceBinderByIntent(
127                         context,
128                         INTENT_FILTER_ACTION,
129                         List.of(
130                                 ODP_MANAGING_SERVICE_PACKAGE_SUFFIX,
131                                 ALT_ODP_MANAGING_SERVICE_PACKAGE_SUFFIX),
132                         SdkLevel.isAtLeastU() ? Context.BIND_ALLOW_ACTIVITY_STARTS : 0,
133                         IOnDevicePersonalizationManagingService.Stub::asInterface));
134     }
135 
136     /** @hide */
137     @VisibleForTesting
OnDevicePersonalizationManager( Context context, AbstractServiceBinder<IOnDevicePersonalizationManagingService> serviceBinder)138     public OnDevicePersonalizationManager(
139             Context context,
140             AbstractServiceBinder<IOnDevicePersonalizationManagingService> serviceBinder) {
141         mContext = context;
142         mServiceBinder = serviceBinder;
143     }
144 
145     /**
146      * Executes an {@link IsolatedService} in the OnDevicePersonalization sandbox. The
147      * platform binds to the specified {@link IsolatedService} in an isolated process
148      * and calls {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)}
149      * with the caller-provided parameters. When the {@link IsolatedService} finishes execution,
150      * the platform returns tokens that refer to the results from the service to the caller.
151      * These tokens can be subsequently used to display results in a
152      * {@link android.view.SurfaceView} within the calling app.
153      *
154      * @param service The {@link ComponentName} of the {@link IsolatedService}.
155      * @param params a {@link PersistableBundle} that is passed from the calling app to the
156      *     {@link IsolatedService}. The expected contents of this parameter are defined
157      *     by the{@link IsolatedService}. The platform does not interpret this parameter.
158      * @param executor the {@link Executor} on which to invoke the callback.
159      * @param receiver This returns a {@link ExecuteResult} object on success or an
160      *     {@link Exception} on failure. If the
161      *     {@link IsolatedService} returned a {@link RenderingConfig} to be displayed,
162      *     {@link ExecuteResult#getSurfacePackageToken()} will return a non-null
163      *     {@link SurfacePackageToken}.
164      *     The {@link SurfacePackageToken} object can be used in a subsequent
165      *     {@link #requestSurfacePackage(SurfacePackageToken, IBinder, int, int, int, Executor,
166      *     OutcomeReceiver)} call to display the result in a view. The returned
167      *     {@link SurfacePackageToken} may be null to indicate that no output is expected to be
168      *     displayed for this request. If the {@link IsolatedService} has returned any output data
169      *     and the calling app is allowlisted to receive data from this service, the
170      *     {@link ExecuteResult#getOutputData()} will return a non-null byte array.
171      *
172      *     In case of an error, the receiver returns one of the following exceptions:
173      *     Returns a {@link android.content.pm.PackageManager.NameNotFoundException} if the handler
174      *     package is not installed or does not have a valid ODP manifest.
175      *     Returns {@link ClassNotFoundException} if the handler class is not found.
176      *     Returns an {@link OnDevicePersonalizationException} if execution of the handler fails.
177      */
execute( @onNull ComponentName service, @NonNull PersistableBundle params, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<ExecuteResult, Exception> receiver )178     public void execute(
179             @NonNull ComponentName service,
180             @NonNull PersistableBundle params,
181             @NonNull @CallbackExecutor Executor executor,
182             @NonNull OutcomeReceiver<ExecuteResult, Exception> receiver
183     ) {
184         Objects.requireNonNull(service);
185         Objects.requireNonNull(params);
186         Objects.requireNonNull(executor);
187         Objects.requireNonNull(receiver);
188         Objects.requireNonNull(service.getPackageName());
189         Objects.requireNonNull(service.getClassName());
190         if (service.getPackageName().isEmpty()) {
191             throw new IllegalArgumentException("missing service package name");
192         }
193         if (service.getClassName().isEmpty()) {
194             throw new IllegalArgumentException("missing service class name");
195         }
196         long startTimeMillis = SystemClock.elapsedRealtime();
197 
198         try {
199             final IOnDevicePersonalizationManagingService odpService =
200                     mServiceBinder.getService(executor);
201 
202             try {
203                 IExecuteCallback callbackWrapper =
204                         new IExecuteCallback.Stub() {
205                             @Override
206                             public void onSuccess(
207                                     Bundle callbackResult, CalleeMetadata calleeMetadata) {
208                                 final long token = Binder.clearCallingIdentity();
209                                 try {
210                                     executor.execute(
211                                             () -> {
212                                                 try {
213                                                     SurfacePackageToken surfacePackageToken = null;
214                                                     if (callbackResult != null) {
215                                                         String tokenString =
216                                                                 callbackResult.getString(
217                                                                         Constants
218                                                                                 .EXTRA_SURFACE_PACKAGE_TOKEN_STRING);
219                                                         if (tokenString != null
220                                                                 && !tokenString.isBlank()) {
221                                                             surfacePackageToken =
222                                                                     new SurfacePackageToken(
223                                                                             tokenString);
224                                                         }
225                                                     }
226                                                     byte[] data =
227                                                             callbackResult.getByteArray(
228                                                                     Constants.EXTRA_OUTPUT_DATA);
229                                                     receiver.onResult(
230                                                             new ExecuteResult(
231                                                                     surfacePackageToken, data));
232                                                 } catch (Exception e) {
233                                                     receiver.onError(e);
234                                                 }
235                                             });
236                                 } finally {
237                                     Binder.restoreCallingIdentity(token);
238                                     logApiCallStats(
239                                             odpService,
240                                             service.getPackageName(),
241                                             Constants.API_NAME_EXECUTE,
242                                             SystemClock.elapsedRealtime() - startTimeMillis,
243                                             calleeMetadata.getServiceEntryTimeMillis() - startTimeMillis,
244                                             SystemClock.elapsedRealtime()
245                                                     - calleeMetadata.getCallbackInvokeTimeMillis(),
246                                             Constants.STATUS_SUCCESS);
247                                 }
248                             }
249 
250                             @Override
251                             public void onError(int errorCode, int isolatedServiceErrorCode,
252                                     String message, CalleeMetadata calleeMetadata) {
253                                 final long token = Binder.clearCallingIdentity();
254                                 try {
255                                     executor.execute(
256                                             () ->
257                                                     receiver.onError(
258                                                             createException(
259                                                                     errorCode,
260                                                                     isolatedServiceErrorCode,
261                                                                     message)));
262                                 } finally {
263                                     Binder.restoreCallingIdentity(token);
264                                     logApiCallStats(
265                                             odpService,
266                                             service.getPackageName(),
267                                             Constants.API_NAME_EXECUTE,
268                                             SystemClock.elapsedRealtime() - startTimeMillis,
269                                             calleeMetadata.getServiceEntryTimeMillis() - startTimeMillis,
270                                             SystemClock.elapsedRealtime()
271                                                     - calleeMetadata.getCallbackInvokeTimeMillis(),
272                                             errorCode);
273                                 }
274                             }
275                         };
276 
277                 Bundle wrappedParams = new Bundle();
278                 wrappedParams.putParcelable(
279                         Constants.EXTRA_APP_PARAMS_SERIALIZED,
280                         new ByteArrayParceledSlice(PersistableBundleUtils.toByteArray(params)));
281                 odpService.execute(
282                         mContext.getPackageName(),
283                         service,
284                         wrappedParams,
285                         new CallerMetadata.Builder().setStartTimeMillis(startTimeMillis).build(),
286                         callbackWrapper);
287             } catch (Exception e) {
288                 logApiCallStats(
289                         odpService,
290                         service.getPackageName(),
291                         Constants.API_NAME_EXECUTE,
292                         SystemClock.elapsedRealtime() - startTimeMillis,
293                         0,
294                         0,
295                         Constants.STATUS_INTERNAL_ERROR);
296                 receiver.onError(e);
297             }
298 
299         } catch (Exception e) {
300             receiver.onError(e);
301         }
302     }
303 
304     /**
305      * Requests a {@link android.view.SurfaceControlViewHost.SurfacePackage} to be inserted into a
306      * {@link android.view.SurfaceView} inside the calling app. The surface package will contain an
307      * {@link android.view.View} with the content from a result of a prior call to
308      * {@code #execute(ComponentName, PersistableBundle, Executor, OutcomeReceiver)} running in
309      * the OnDevicePersonalization sandbox.
310      *
311      * @param surfacePackageToken a reference to a {@link SurfacePackageToken} returned by a prior
312      *     call to {@code #execute(ComponentName, PersistableBundle, Executor, OutcomeReceiver)}.
313      * @param surfaceViewHostToken the hostToken of the {@link android.view.SurfaceView}, which is
314      *     returned by {@link android.view.SurfaceView#getHostToken()} after the
315      *     {@link android.view.SurfaceView} has been added to the view hierarchy.
316      * @param displayId the integer ID of the logical display on which to display the
317      *     {@link android.view.SurfaceControlViewHost.SurfacePackage}, returned by
318      *     {@code Context.getDisplay().getDisplayId()}.
319      * @param width the width of the {@link android.view.SurfaceControlViewHost.SurfacePackage}
320      *     in pixels.
321      * @param height the height of the {@link android.view.SurfaceControlViewHost.SurfacePackage}
322      *     in pixels.
323      * @param executor the {@link Executor} on which to invoke the callback
324      * @param receiver This either returns a
325      *     {@link android.view.SurfaceControlViewHost.SurfacePackage} on success, or
326      *     {@link Exception} on failure. The exception type is
327      *     {@link OnDevicePersonalizationException} if execution of the handler fails.
328      */
requestSurfacePackage( @onNull SurfacePackageToken surfacePackageToken, @NonNull IBinder surfaceViewHostToken, int displayId, int width, int height, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<SurfaceControlViewHost.SurfacePackage, Exception> receiver )329     public void requestSurfacePackage(
330             @NonNull SurfacePackageToken surfacePackageToken,
331             @NonNull IBinder surfaceViewHostToken,
332             int displayId,
333             int width,
334             int height,
335             @NonNull @CallbackExecutor Executor executor,
336             @NonNull OutcomeReceiver<SurfaceControlViewHost.SurfacePackage, Exception> receiver
337     ) {
338         Objects.requireNonNull(surfacePackageToken);
339         Objects.requireNonNull(surfaceViewHostToken);
340         Objects.requireNonNull(executor);
341         Objects.requireNonNull(receiver);
342         if (width <= 0) {
343             throw new IllegalArgumentException("width must be > 0");
344         }
345 
346         if (height <= 0) {
347             throw new IllegalArgumentException("height must be > 0");
348         }
349 
350         if (displayId < 0) {
351             throw new IllegalArgumentException("displayId must be >= 0");
352         }
353         long startTimeMillis = SystemClock.elapsedRealtime();
354 
355         try {
356             final IOnDevicePersonalizationManagingService service =
357                     Objects.requireNonNull(mServiceBinder.getService(executor));
358             long serviceInvokedTimeMillis = SystemClock.elapsedRealtime();
359 
360             try {
361                 IRequestSurfacePackageCallback callbackWrapper =
362                         new IRequestSurfacePackageCallback.Stub() {
363                             @Override
364                             public void onSuccess(
365                                     SurfaceControlViewHost.SurfacePackage surfacePackage,
366                                     CalleeMetadata calleeMetadata) {
367                                 final long token = Binder.clearCallingIdentity();
368                                 try {
369                                     executor.execute(
370                                             () -> {
371                                                 receiver.onResult(surfacePackage);
372                                             });
373                                 } finally {
374                                     Binder.restoreCallingIdentity(token);
375                                     logApiCallStats(
376                                             service,
377                                             "",
378                                             Constants.API_NAME_REQUEST_SURFACE_PACKAGE,
379                                             SystemClock.elapsedRealtime() - startTimeMillis,
380                                             0,
381                                             SystemClock.elapsedRealtime()
382                                                     - calleeMetadata.getCallbackInvokeTimeMillis(),
383                                             Constants.STATUS_SUCCESS);
384                                 }
385 
386                             }
387 
388                             @Override
389                             public void onError(int errorCode, int isolatedServiceErrorCode,
390                                     String message, CalleeMetadata calleeMetadata) {
391                                 final long token = Binder.clearCallingIdentity();
392                                 try {
393                                     executor.execute(
394                                             () ->
395                                                     receiver.onError(
396                                                             createException(
397                                                                     errorCode,
398                                                                     isolatedServiceErrorCode,
399                                                                     message)));
400                                 } finally {
401                                     Binder.restoreCallingIdentity(token);
402                                     logApiCallStats(
403                                             service, "",
404                                             Constants.API_NAME_REQUEST_SURFACE_PACKAGE,
405                                             SystemClock.elapsedRealtime() - startTimeMillis,
406                                             0,
407                                             SystemClock.elapsedRealtime()
408                                                     - calleeMetadata.getCallbackInvokeTimeMillis(),
409                                             errorCode);
410                                 }
411                             }
412                         };
413 
414                 service.requestSurfacePackage(
415                         surfacePackageToken.getTokenString(),
416                         surfaceViewHostToken,
417                         displayId,
418                         width,
419                         height,
420                         new CallerMetadata.Builder().setStartTimeMillis(startTimeMillis).build(),
421                         callbackWrapper);
422                 logApiCallStats(
423                         service,
424                         "",
425                         Constants.API_NAME_REQUEST_SURFACE_PACKAGE,
426                         SystemClock.elapsedRealtime() - startTimeMillis,
427                         SystemClock.elapsedRealtime() - serviceInvokedTimeMillis,
428                         0,
429                         Constants.STATUS_SUCCESS);
430 
431             } catch (Exception e) {
432                 logApiCallStats(
433                         service,
434                         "",
435                         Constants.API_NAME_REQUEST_SURFACE_PACKAGE,
436                         SystemClock.elapsedRealtime() - startTimeMillis,
437                         0,
438                         0,
439                         Constants.STATUS_INTERNAL_ERROR);
440                 receiver.onError(e);
441             }
442 
443         } catch (Exception e) {
444             receiver.onError(e);
445         }
446     }
447 
convertMessage(int errorCode, String message)448     private static String convertMessage(int errorCode, String message) {
449         // Defer to existing message received from service callback if it is non-empty, else
450         // translate the internal error codes into error messages.
451         if (message != null && !message.isBlank()) {
452             return message;
453         }
454 
455         switch (errorCode) {
456             case Constants.STATUS_INTERNAL_ERROR:
457                 return ODP_INTERNAL_ERROR_MESSAGE;
458             case Constants.STATUS_SERVICE_FAILED:
459                 return ISOLATED_SERVICE_ERROR_MESSAGE;
460             case Constants.STATUS_PERSONALIZATION_DISABLED:
461                 return ODP_DISABLED_ERROR_MESSAGE;
462             default:
463                 sLogger.w(TAG + "Unexpected error code while creating exception: " + errorCode);
464                 return "";
465         }
466     }
467 
createException( int errorCode, int isolatedServiceErrorCode, String message)468     private static Exception createException(
469             int errorCode, int isolatedServiceErrorCode, String message) {
470         if (errorCode == Constants.STATUS_NAME_NOT_FOUND) {
471             return new PackageManager.NameNotFoundException();
472         } else if (errorCode == Constants.STATUS_CLASS_NOT_FOUND) {
473             return new ClassNotFoundException();
474         } else if (errorCode == Constants.STATUS_SERVICE_FAILED) {
475             if (isolatedServiceErrorCode > 0 && isolatedServiceErrorCode < 128) {
476                 return new OnDevicePersonalizationException(
477                         OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
478                         new IsolatedServiceException(isolatedServiceErrorCode));
479             } else {
480                 return new OnDevicePersonalizationException(
481                         OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
482                         convertMessage(errorCode, message));
483             }
484         } else if (errorCode == Constants.STATUS_PERSONALIZATION_DISABLED) {
485             return new OnDevicePersonalizationException(
486                     OnDevicePersonalizationException.ERROR_PERSONALIZATION_DISABLED,
487                     convertMessage(errorCode, message));
488         } else {
489             return new IllegalStateException(convertMessage(errorCode, message));
490         }
491     }
492 
logApiCallStats( IOnDevicePersonalizationManagingService service, String sdkPackageName, int apiName, long latencyMillis, long rpcCallLatencyMillis, long rpcReturnLatencyMillis, int responseCode)493     private void logApiCallStats(
494             IOnDevicePersonalizationManagingService service,
495             String sdkPackageName,
496             int apiName,
497             long latencyMillis,
498             long rpcCallLatencyMillis,
499             long rpcReturnLatencyMillis,
500             int responseCode) {
501         try {
502             if (service != null) {
503                 service.logApiCallStats(sdkPackageName, apiName, latencyMillis,
504                         rpcCallLatencyMillis, rpcReturnLatencyMillis, responseCode);
505             }
506         } catch (Exception e) {
507             sLogger.e(e, TAG + ": Error logging API call stats");
508         }
509     }
510 }
511