• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ondevicepersonalization;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.ServiceConnection;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.ondevicepersonalization.aidl.IExecuteCallback;
29 import android.ondevicepersonalization.aidl.IOnDevicePersonalizationManagingService;
30 import android.ondevicepersonalization.aidl.IRequestSurfacePackageCallback;
31 import android.os.Bundle;
32 import android.os.IBinder;
33 import android.os.OutcomeReceiver;
34 import android.os.PersistableBundle;
35 import android.os.RemoteException;
36 import android.util.Slog;
37 import android.view.SurfaceControlViewHost;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.concurrent.CountDownLatch;
42 import java.util.concurrent.Executor;
43 import java.util.concurrent.TimeUnit;
44 
45 /**
46  * OnDevicePersonalizationManager.
47  *
48  * @hide
49  */
50 public class OnDevicePersonalizationManager {
51     public static final String ON_DEVICE_PERSONALIZATION_SERVICE =
52             "on_device_personalization_service";
53 
54     /**
55      * The name of key to be used in the Bundle fields of {@link #requestSurfacePackage()},
56      * its value should define the integer width of the {@link SurfacePackage} in pixels.
57      */
58     public static final String EXTRA_WIDTH_IN_PIXELS =
59             "android.ondevicepersonalization.extra.WIDTH_IN_PIXELS";
60     /**
61      * The name of key to be used in the Bundle fields of {@link #requestSurfacePackage()},
62      * its value should define the integer height of the {@link SurfacePackage} in pixels.
63      */
64     public static final String EXTRA_HEIGHT_IN_PIXELS =
65             "android.ondevicepersonalization.extra.HEIGHT_IN_PIXELS";
66     /**
67      * The name of key to be used in the Bundle fields of {@link #requestSurfacePackage()},
68      * its value should define the integer ID of the logical
69      * display to display the {@link SurfacePackage}.
70      */
71     public static final String EXTRA_DISPLAY_ID =
72             "android.ondevicepersonalization.extra.DISPLAY_ID";
73 
74     /**
75      * The name of key to be used in the Bundle fields of {@link #requestSurfacePackage()},
76      * its value should present the token returned by {@link
77      * android.view.SurfaceView#getHostToken()} once the {@link android.view.SurfaceView}
78      * has been added to the view hierarchy. Only a non-null value is accepted to enable
79      * ANR reporting.
80      */
81     public static final String EXTRA_HOST_TOKEN =
82             "android.ondevicepersonalization.extra.HOST_TOKEN";
83 
84     /**
85      * The name of key to be used in the Bundle fields of {@link #requestSurfacePackage()},
86      * its value should define a {@link PersistableBundle} that is passed to the
87      * {@link IsolatedComputationService}.
88      */
89     public static final String EXTRA_APP_PARAMS =
90             "android.ondevicepersonalization.extra.APP_PARAMS";
91 
92     /**
93      * The name of key in the Bundle which is passed to the {@code onResult} function of the {@link
94      * OutcomeReceiver} which is field of {@link #requestSurfacePackage()},
95      * its value presents the requested {@link SurfacePackage}.
96      */
97     public static final String EXTRA_SURFACE_PACKAGE =
98             "android.ondevicepersonalization.extra.SURFACE_PACKAGE";
99 
100     private boolean mBound = false;
101     private static final String TAG = "OdpManager";
102 
103     private IOnDevicePersonalizationManagingService mService;
104     private final Context mContext;
105 
OnDevicePersonalizationManager(Context context)106     public OnDevicePersonalizationManager(Context context) {
107         mContext = context;
108     }
109 
110     private final CountDownLatch mConnectionLatch = new CountDownLatch(1);
111 
112     private final ServiceConnection mConnection =
113             new ServiceConnection() {
114                 @Override
115                 public void onServiceConnected(ComponentName name, IBinder service) {
116                     mService = IOnDevicePersonalizationManagingService.Stub.asInterface(service);
117                     mBound = true;
118                     mConnectionLatch.countDown();
119                 }
120 
121                 @Override
122                 public void onNullBinding(ComponentName name) {
123                     mBound = false;
124                     mConnectionLatch.countDown();
125                 }
126 
127                 @Override
128                 public void onServiceDisconnected(ComponentName name) {
129                     mService = null;
130                     mBound = false;
131                 }
132             };
133 
134     private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
135     private static final String VERSION = "1.0";
136 
137     /**
138      * Gets OnDevicePersonalization version.
139      * This function is a temporary place holder. It will be removed when new APIs are added.
140      *
141      * @hide
142      */
getVersion()143     public String getVersion() {
144         return VERSION;
145     }
146 
147     /**
148      * Executes a {@link IsolatedComputationHandler} in the OnDevicePersonalization sandbox.
149      *
150      * @param servicePackageName The name of the package containing the handler.
151      * @param params a {@link PersistableBundle} passed from the calling app to the handler.
152      * @param executor the {@link Executor} on which to invoke the callback
153      * @param receiver This returns a list of {@link SlotResultHandle} objects, each of which is an
154      *     opaque reference to a {@link SlotResult} returned by a
155      *     {@link IsolatedComputationHandler}, or an {@link Exception} on failure. The returned
156      *     {@link SlotResultHandle} objects can be used in a subsequent
157      *     {@link requestSurfacePackage} call to display the result in a view.
158      */
execute( @onNull String servicePackageName, @NonNull PersistableBundle params, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<List<SlotResultHandle>, Exception> receiver )159     public void execute(
160             @NonNull String servicePackageName,
161             @NonNull PersistableBundle params,
162             @NonNull @CallbackExecutor Executor executor,
163             @NonNull OutcomeReceiver<List<SlotResultHandle>, Exception> receiver
164     ) {
165         try {
166             bindService(executor);
167 
168             IExecuteCallback callbackWrapper = new IExecuteCallback.Stub() {
169                 @Override
170                 public void onSuccess(
171                         @NonNull List<String> slotResultTokens) {
172                     executor.execute(() -> {
173                         try {
174                             ArrayList<SlotResultHandle> slotResults =
175                                     new ArrayList<>(slotResultTokens.size());
176                             for (String token : slotResultTokens) {
177                                 if (token == null) {
178                                     slotResults.add(null);
179                                 } else {
180                                     slotResults.add(new SlotResultHandle(token));
181                                 }
182                             }
183                             receiver.onResult(slotResults);
184                         } catch (Exception e) {
185                             receiver.onError(e);
186                         }
187                     });
188                 }
189 
190                 @Override
191                 public void onError(int errorCode) {
192                     executor.execute(() -> receiver.onError(
193                             new OnDevicePersonalizationException(errorCode)));
194                 }
195             };
196 
197             mService.execute(
198                     mContext.getPackageName(), servicePackageName, params, callbackWrapper);
199 
200         } catch (Exception e) {
201             receiver.onError(e);
202         }
203     }
204 
205     /**
206      * Requests a surface package. The surface package will contain a {@link WebView} with html from
207      * a {@link IsolatedComputationHandler} running in the OnDevicePersonalization sandbox.
208      *
209      * @param slotResultHandle a reference to a {@link SlotResultHandle} returned by a prior call to
210      *     {@link execute}.
211      * @param params the parameters from the client application, it must
212      *     contain the following params: (EXTRA_WIDTH_IN_PIXELS, EXTRA_HEIGHT_IN_PIXELS,
213      *     EXTRA_DISPLAY_ID, EXTRA_HOST_TOKEN). If any of these params is missing, an
214      *     IllegalArgumentException will be thrown.
215      * @param executor the {@link Executor} on which to invoke the callback
216      * @param receiver This either returns a {@link Bundle} on success which should contain the key
217      *     EXTRA_SURFACE_PACKAGE with value of {@link SurfacePackage} response, or {@link
218      *     Exception} on failure.
219      * @throws IllegalArgumentException if any of the following params (EXTRA_WIDTH_IN_PIXELS,
220      *     EXTRA_HEIGHT_IN_PIXELS, EXTRA_DISPLAY_ID, EXTRA_HOST_TOKEN) are missing from the Bundle
221      *     or passed with the wrong value or type.
222      *
223      * @hide
224      */
requestSurfacePackage( @onNull SlotResultHandle slotResultHandle, IBinder hostToken, int displayId, int width, int height, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<SurfaceControlViewHost.SurfacePackage, Exception> receiver )225     public void requestSurfacePackage(
226             @NonNull SlotResultHandle slotResultHandle,
227             IBinder hostToken,
228             int displayId,
229             int width,
230             int height,
231             @NonNull @CallbackExecutor Executor executor,
232             @NonNull OutcomeReceiver<SurfaceControlViewHost.SurfacePackage, Exception> receiver
233     ) {
234         try {
235             bindService(executor);
236 
237             IRequestSurfacePackageCallback callbackWrapper =
238                     new IRequestSurfacePackageCallback.Stub() {
239                         @Override
240                         public void onSuccess(
241                                 @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) {
242                             executor.execute(() -> {
243                                 receiver.onResult(surfacePackage);
244                             });
245                         }
246 
247                         @Override
248                         public void onError(int errorCode) {
249                             executor.execute(() -> receiver.onError(
250                                     new OnDevicePersonalizationException(errorCode)));
251                         }
252                     };
253 
254             mService.requestSurfacePackage(
255                     slotResultHandle.getSlotResultToken(), hostToken, displayId,
256                     width, height, callbackWrapper);
257 
258         } catch (InterruptedException
259                 | NullPointerException
260                 | RemoteException e) {
261             receiver.onError(e);
262         }
263     }
264 
265     /** Bind to the service, if not already bound. */
bindService(@onNull Executor executor)266     private void bindService(@NonNull Executor executor) throws InterruptedException {
267         if (!mBound) {
268             Intent intent = new Intent("android.OnDevicePersonalizationService");
269             ComponentName serviceComponent =
270                     resolveService(intent, mContext.getPackageManager());
271             if (serviceComponent == null) {
272                 Slog.e(TAG, "Invalid component for ondevicepersonalization service");
273                 return;
274             }
275 
276             intent.setComponent(serviceComponent);
277             boolean r = mContext.bindService(
278                     intent, Context.BIND_AUTO_CREATE, executor, mConnection);
279             if (!r) {
280                 return;
281             }
282             mConnectionLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS);
283         }
284     }
285 
286     /**
287      * Find the ComponentName of the service, given its intent and package manager.
288      *
289      * @return ComponentName of the service. Null if the service is not found.
290      */
resolveService( @onNull Intent intent, @NonNull PackageManager pm)291     private @Nullable ComponentName resolveService(
292             @NonNull Intent intent, @NonNull PackageManager pm) {
293         List<ResolveInfo> services =
294                 pm.queryIntentServices(intent, PackageManager.ResolveInfoFlags.of(0));
295         if (services == null || services.isEmpty()) {
296             Slog.e(TAG, "Failed to find ondevicepersonalization service");
297             return null;
298         }
299 
300         for (int i = 0; i < services.size(); i++) {
301             ResolveInfo ri = services.get(i);
302             ComponentName resolved =
303                     new ComponentName(ri.serviceInfo.packageName, ri.serviceInfo.name);
304             // There should only be one matching service inside the given package.
305             // If there's more than one, return the first one found.
306             return resolved;
307         }
308         Slog.e(TAG, "Didn't find any matching ondevicepersonalization service.");
309         return null;
310     }
311 }
312