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