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.app.sdksandbox; 18 19 import static android.app.sdksandbox.SdkSandboxManager.SDK_SANDBOX_SERVICE; 20 21 import android.annotation.CallbackExecutor; 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SdkConstant; 26 import android.annotation.SystemApi; 27 import android.annotation.SystemService; 28 import android.annotation.TestApi; 29 import android.app.Activity; 30 import android.app.sdksandbox.sdkprovider.SdkSandboxActivityHandler; 31 import android.app.sdksandbox.sdkprovider.SdkSandboxController; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.SharedPreferences; 35 import android.os.Build; 36 import android.os.Bundle; 37 import android.os.IBinder; 38 import android.os.OutcomeReceiver; 39 import android.os.RemoteException; 40 import android.util.Log; 41 import android.view.SurfaceControlViewHost.SurfacePackage; 42 43 import androidx.annotation.RequiresApi; 44 45 import com.android.internal.annotations.GuardedBy; 46 import com.android.modules.utils.build.SdkLevel; 47 48 import java.lang.annotation.Retention; 49 import java.lang.annotation.RetentionPolicy; 50 import java.util.ArrayList; 51 import java.util.List; 52 import java.util.Objects; 53 import java.util.Set; 54 import java.util.concurrent.Executor; 55 56 /** 57 * Provides APIs to load {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE SDKs} into the 58 * SDK sandbox process, and then interact with them. 59 * 60 * <p>SDK sandbox is a java process running in a separate uid range. Each app may have its own SDK 61 * sandbox process. 62 * 63 * <p>The app first needs to declare SDKs it depends on in its manifest using the {@code 64 * <uses-sdk-library>} tag. Apps may only load SDKs they depend on into the SDK sandbox. 65 * 66 * @see android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE 67 * @see <a href="https://developer.android.com/design-for-safety/ads/sdk-runtime">SDK Runtime design 68 * proposal</a> 69 */ 70 @SystemService(SDK_SANDBOX_SERVICE) 71 public final class SdkSandboxManager { 72 73 /** 74 * Use with {@link Context#getSystemService(String)} to retrieve an {@link SdkSandboxManager} 75 * for interacting with the SDKs belonging to this client application. 76 */ 77 public static final String SDK_SANDBOX_SERVICE = "sdk_sandbox"; 78 79 /** 80 * SDK sandbox process is not available. 81 * 82 * <p>This indicates that the SDK sandbox process is not available, either because it has died, 83 * disconnected or was not created in the first place. 84 */ 85 public static final int SDK_SANDBOX_PROCESS_NOT_AVAILABLE = 503; 86 87 /** 88 * SDK not found. 89 * 90 * <p>This indicates that client application tried to load a non-existing SDK by calling {@link 91 * SdkSandboxManager#loadSdk(String, Bundle, Executor, OutcomeReceiver)}. 92 */ 93 public static final int LOAD_SDK_NOT_FOUND = 100; 94 95 /** 96 * SDK is already loaded. 97 * 98 * <p>This indicates that client application tried to reload the same SDK by calling {@link 99 * SdkSandboxManager#loadSdk(String, Bundle, Executor, OutcomeReceiver)} after being 100 * successfully loaded. 101 */ 102 public static final int LOAD_SDK_ALREADY_LOADED = 101; 103 104 /** 105 * SDK error after being loaded. 106 * 107 * <p>This indicates that the SDK encountered an error during post-load initialization. The 108 * details of this can be obtained from the Bundle returned in {@link LoadSdkException} through 109 * the {@link OutcomeReceiver} passed in to {@link SdkSandboxManager#loadSdk}. 110 */ 111 public static final int LOAD_SDK_SDK_DEFINED_ERROR = 102; 112 113 /** 114 * SDK sandbox is disabled. 115 * 116 * <p>This indicates that the SDK sandbox is disabled. Any subsequent attempts to load SDKs in 117 * this boot will also fail. 118 */ 119 public static final int LOAD_SDK_SDK_SANDBOX_DISABLED = 103; 120 121 /** 122 * Internal error while loading SDK. 123 * 124 * <p>This indicates a generic internal error happened while applying the call from client 125 * application. 126 */ 127 public static final int LOAD_SDK_INTERNAL_ERROR = 500; 128 129 /** 130 * Action name for the intent which starts {@link Activity} in SDK sandbox. 131 * 132 * <p>System services would know if the intent is created to start {@link Activity} in sandbox 133 * by comparing the action of the intent to the value of this field. 134 * 135 * <p>This intent should contain an extra param with key equals to {@link 136 * #EXTRA_SANDBOXED_ACTIVITY_HANDLER} and value equals to the {@link IBinder} that identifies 137 * the {@link SdkSandboxActivityHandler} that registered before by an SDK. If the extra param is 138 * missing, the {@link Activity} will fail to start. 139 * 140 * @hide 141 */ 142 @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) 143 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) 144 public static final String ACTION_START_SANDBOXED_ACTIVITY = 145 "android.app.sdksandbox.action.START_SANDBOXED_ACTIVITY"; 146 147 /** 148 * The key for an element in {@link Activity} intent extra params, the value is an {@link 149 * SdkSandboxActivityHandler} registered by an SDK. 150 * 151 * @hide 152 */ 153 public static final String EXTRA_SANDBOXED_ACTIVITY_HANDLER = 154 "android.app.sdksandbox.extra.SANDBOXED_ACTIVITY_HANDLER"; 155 156 private static final String TAG = "SdkSandboxManager"; 157 158 /** @hide */ 159 @IntDef( 160 value = { 161 LOAD_SDK_NOT_FOUND, 162 LOAD_SDK_ALREADY_LOADED, 163 LOAD_SDK_SDK_DEFINED_ERROR, 164 LOAD_SDK_SDK_SANDBOX_DISABLED, 165 LOAD_SDK_INTERNAL_ERROR, 166 SDK_SANDBOX_PROCESS_NOT_AVAILABLE 167 }) 168 @Retention(RetentionPolicy.SOURCE) 169 public @interface LoadSdkErrorCode {} 170 171 /** Internal error while requesting a {@link SurfacePackage}. 172 * 173 * <p>This indicates a generic internal error happened while requesting a 174 * {@link SurfacePackage}. 175 */ 176 public static final int REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR = 700; 177 178 /** 179 * SDK is not loaded while requesting a {@link SurfacePackage}. 180 * 181 * <p>This indicates that the SDK for which the {@link SurfacePackage} is being requested is not 182 * loaded, either because the sandbox died or because it was not loaded in the first place. 183 */ 184 public static final int REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED = 701; 185 186 /** @hide */ 187 @IntDef( 188 prefix = "REQUEST_SURFACE_PACKAGE_", 189 value = { 190 REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR, 191 REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED 192 }) 193 @Retention(RetentionPolicy.SOURCE) 194 public @interface RequestSurfacePackageErrorCode {} 195 196 /** 197 * SDK sandbox is disabled. 198 * 199 * <p>{@link SdkSandboxManager} APIs are hidden. Attempts at calling them will result in {@link 200 * UnsupportedOperationException}. 201 */ 202 public static final int SDK_SANDBOX_STATE_DISABLED = 0; 203 204 /** 205 * SDK sandbox is enabled. 206 * 207 * <p>App can use {@link SdkSandboxManager} APIs to load {@code SDKs} it depends on into the 208 * corresponding SDK sandbox process. 209 */ 210 public static final int SDK_SANDBOX_STATE_ENABLED_PROCESS_ISOLATION = 2; 211 212 /** @hide */ 213 @Retention(RetentionPolicy.SOURCE) 214 @IntDef(prefix = "SDK_SANDBOX_STATUS_", value = { 215 SDK_SANDBOX_STATE_DISABLED, 216 SDK_SANDBOX_STATE_ENABLED_PROCESS_ISOLATION, 217 }) 218 public @interface SdkSandboxState {} 219 220 /** 221 * The name of key to be used in the Bundle fields of {@link #requestSurfacePackage(String, 222 * Bundle, Executor, OutcomeReceiver)}, its value should define the integer width of the {@link 223 * SurfacePackage} in pixels. 224 */ 225 public static final String EXTRA_WIDTH_IN_PIXELS = 226 "android.app.sdksandbox.extra.WIDTH_IN_PIXELS"; 227 /** 228 * The name of key to be used in the Bundle fields of {@link #requestSurfacePackage(String, 229 * Bundle, Executor, OutcomeReceiver)}, its value should define the integer height of the {@link 230 * SurfacePackage} in pixels. 231 */ 232 public static final String EXTRA_HEIGHT_IN_PIXELS = 233 "android.app.sdksandbox.extra.HEIGHT_IN_PIXELS"; 234 /** 235 * The name of key to be used in the Bundle fields of {@link #requestSurfacePackage(String, 236 * Bundle, Executor, OutcomeReceiver)}, its value should define the integer ID of the logical 237 * display to display the {@link SurfacePackage}. 238 */ 239 public static final String EXTRA_DISPLAY_ID = "android.app.sdksandbox.extra.DISPLAY_ID"; 240 241 /** 242 * The name of key to be used in the Bundle fields of {@link #requestSurfacePackage(String, 243 * Bundle, Executor, OutcomeReceiver)}, its value should present the token returned by {@link 244 * android.view.SurfaceView#getHostToken()} once the {@link android.view.SurfaceView} has been 245 * added to the view hierarchy. Only a non-null value is accepted to enable ANR reporting. 246 */ 247 public static final String EXTRA_HOST_TOKEN = "android.app.sdksandbox.extra.HOST_TOKEN"; 248 249 /** 250 * The name of key in the Bundle which is passed to the {@code onResult} function of the {@link 251 * OutcomeReceiver} which is field of {@link #requestSurfacePackage(String, Bundle, Executor, 252 * OutcomeReceiver)}, its value presents the requested {@link SurfacePackage}. 253 */ 254 public static final String EXTRA_SURFACE_PACKAGE = 255 "android.app.sdksandbox.extra.SURFACE_PACKAGE"; 256 257 private final ISdkSandboxManager mService; 258 private final Context mContext; 259 260 @GuardedBy("mLifecycleCallbacks") 261 private final ArrayList<SdkSandboxProcessDeathCallbackProxy> mLifecycleCallbacks = 262 new ArrayList<>(); 263 264 private final SharedPreferencesSyncManager mSyncManager; 265 266 /** @hide */ SdkSandboxManager(@onNull Context context, @NonNull ISdkSandboxManager binder)267 public SdkSandboxManager(@NonNull Context context, @NonNull ISdkSandboxManager binder) { 268 mContext = Objects.requireNonNull(context, "context should not be null"); 269 mService = Objects.requireNonNull(binder, "binder should not be null"); 270 // TODO(b/239403323): There can be multiple package in the same app process 271 mSyncManager = SharedPreferencesSyncManager.getInstance(context, binder); 272 } 273 274 /** Returns the current state of the availability of the SDK sandbox feature. */ 275 @SdkSandboxState getSdkSandboxState()276 public static int getSdkSandboxState() { 277 return SDK_SANDBOX_STATE_ENABLED_PROCESS_ISOLATION; 278 } 279 280 /** 281 * Stops the SDK sandbox process corresponding to the app. 282 * 283 * @hide 284 */ 285 @TestApi 286 @RequiresPermission("com.android.app.sdksandbox.permission.STOP_SDK_SANDBOX") stopSdkSandbox()287 public void stopSdkSandbox() { 288 try { 289 mService.stopSdkSandbox(mContext.getPackageName()); 290 } catch (RemoteException e) { 291 throw e.rethrowFromSystemServer(); 292 } 293 } 294 295 /** 296 * Adds a callback which gets registered for SDK sandbox lifecycle events, such as SDK sandbox 297 * death. If the sandbox has not yet been created when this is called, the request will be 298 * stored until a sandbox is created, at which point it is activated for that sandbox. Multiple 299 * callbacks can be added to detect death and will not be removed when the sandbox dies. 300 * 301 * @param callbackExecutor the {@link Executor} on which to invoke the callback 302 * @param callback the {@link SdkSandboxProcessDeathCallback} which will receive SDK sandbox 303 * lifecycle events. 304 */ addSdkSandboxProcessDeathCallback( @onNull @allbackExecutor Executor callbackExecutor, @NonNull SdkSandboxProcessDeathCallback callback)305 public void addSdkSandboxProcessDeathCallback( 306 @NonNull @CallbackExecutor Executor callbackExecutor, 307 @NonNull SdkSandboxProcessDeathCallback callback) { 308 Objects.requireNonNull(callbackExecutor, "callbackExecutor should not be null"); 309 Objects.requireNonNull(callback, "callback should not be null"); 310 311 synchronized (mLifecycleCallbacks) { 312 final SdkSandboxProcessDeathCallbackProxy callbackProxy = 313 new SdkSandboxProcessDeathCallbackProxy(callbackExecutor, callback); 314 try { 315 mService.addSdkSandboxProcessDeathCallback( 316 mContext.getPackageName(), 317 /*timeAppCalledSystemServer=*/ System.currentTimeMillis(), 318 callbackProxy); 319 } catch (RemoteException e) { 320 throw e.rethrowFromSystemServer(); 321 } 322 mLifecycleCallbacks.add(callbackProxy); 323 } 324 } 325 326 /** 327 * Removes an {@link SdkSandboxProcessDeathCallback} that was previously added using {@link 328 * SdkSandboxManager#addSdkSandboxProcessDeathCallback(Executor, 329 * SdkSandboxProcessDeathCallback)} 330 * 331 * @param callback the {@link SdkSandboxProcessDeathCallback} which was previously added using 332 * {@link SdkSandboxManager#addSdkSandboxProcessDeathCallback(Executor, 333 * SdkSandboxProcessDeathCallback)} 334 */ removeSdkSandboxProcessDeathCallback( @onNull SdkSandboxProcessDeathCallback callback)335 public void removeSdkSandboxProcessDeathCallback( 336 @NonNull SdkSandboxProcessDeathCallback callback) { 337 Objects.requireNonNull(callback, "callback should not be null"); 338 synchronized (mLifecycleCallbacks) { 339 for (int i = mLifecycleCallbacks.size() - 1; i >= 0; i--) { 340 final SdkSandboxProcessDeathCallbackProxy callbackProxy = 341 mLifecycleCallbacks.get(i); 342 if (callbackProxy.callback == callback) { 343 try { 344 mService.removeSdkSandboxProcessDeathCallback( 345 mContext.getPackageName(), 346 /*timeAppCalledSystemServer=*/ System.currentTimeMillis(), 347 callbackProxy); 348 } catch (RemoteException e) { 349 throw e.rethrowFromSystemServer(); 350 } 351 mLifecycleCallbacks.remove(i); 352 } 353 } 354 } 355 } 356 357 /** 358 * Loads SDK in an SDK sandbox java process. 359 * 360 * <p>Loads SDK library with {@code sdkName} to an SDK sandbox process asynchronously. The 361 * caller will be notified through the {@code receiver}. 362 * 363 * <p>The caller should already declare {@code SDKs} it depends on in its manifest using {@code 364 * <uses-sdk-library>} tag. The caller may only load {@code SDKs} it depends on into the SDK 365 * sandbox. 366 * 367 * <p>When the client application loads the first SDK, a new SDK sandbox process will be 368 * created. If a sandbox has already been created for the client application, additional SDKs 369 * will be loaded into the same sandbox. 370 * 371 * <p>This API may only be called while the caller is running in the foreground. Calls from the 372 * background will result in returning {@link LoadSdkException} in the {@code receiver}. 373 * 374 * @param sdkName name of the SDK to be loaded. 375 * @param params additional parameters to be passed to the SDK in the form of a {@link Bundle} 376 * as agreed between the client and the SDK. 377 * @param executor the {@link Executor} on which to invoke the receiver. 378 * @param receiver This either receives a {@link SandboxedSdk} on a successful run, or {@link 379 * LoadSdkException}. 380 */ loadSdk( @onNull String sdkName, @NonNull Bundle params, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<SandboxedSdk, LoadSdkException> receiver)381 public void loadSdk( 382 @NonNull String sdkName, 383 @NonNull Bundle params, 384 @NonNull @CallbackExecutor Executor executor, 385 @NonNull OutcomeReceiver<SandboxedSdk, LoadSdkException> receiver) { 386 Objects.requireNonNull(sdkName, "sdkName should not be null"); 387 Objects.requireNonNull(params, "params should not be null"); 388 Objects.requireNonNull(executor, "executor should not be null"); 389 Objects.requireNonNull(receiver, "receiver should not be null"); 390 final LoadSdkReceiverProxy callbackProxy = 391 new LoadSdkReceiverProxy(executor, receiver, mService); 392 393 IBinder appProcessToken; 394 // Context.getProcessToken() only exists on U+. 395 if (SdkLevel.isAtLeastU()) { 396 appProcessToken = mContext.getProcessToken(); 397 } else { 398 appProcessToken = null; 399 } 400 try { 401 mService.loadSdk( 402 mContext.getPackageName(), 403 appProcessToken, 404 sdkName, 405 /*timeAppCalledSystemServer=*/ System.currentTimeMillis(), 406 params, 407 callbackProxy); 408 } catch (RemoteException e) { 409 throw e.rethrowFromSystemServer(); 410 } 411 } 412 413 /** 414 * Fetches information about SDKs that are loaded in the sandbox. 415 * 416 * @return List of {@link SandboxedSdk} containing all currently loaded SDKs. 417 */ getSandboxedSdks()418 public @NonNull List<SandboxedSdk> getSandboxedSdks() { 419 try { 420 return mService.getSandboxedSdks( 421 mContext.getPackageName(), 422 /*timeAppCalledSystemServer=*/ System.currentTimeMillis()); 423 } catch (RemoteException e) { 424 throw e.rethrowFromSystemServer(); 425 } 426 } 427 428 /** 429 * Unloads an SDK that has been previously loaded by the caller. 430 * 431 * <p>It is not guaranteed that the memory allocated for this SDK will be freed immediately. All 432 * subsequent calls to {@link #requestSurfacePackage(String, Bundle, Executor, OutcomeReceiver)} 433 * for the given {@code sdkName} will fail. 434 * 435 * <p>This API may only be called while the caller is running in the foreground. Calls from the 436 * background will result in a {@link SecurityException} being thrown. 437 * 438 * @param sdkName name of the SDK to be unloaded. 439 */ unloadSdk(@onNull String sdkName)440 public void unloadSdk(@NonNull String sdkName) { 441 Objects.requireNonNull(sdkName, "sdkName should not be null"); 442 try { 443 mService.unloadSdk( 444 mContext.getPackageName(), 445 sdkName, 446 /*timeAppCalledSystemServer=*/ System.currentTimeMillis()); 447 } catch (RemoteException e) { 448 throw e.rethrowFromSystemServer(); 449 } 450 } 451 452 /** 453 * Sends a request for a surface package to the SDK. 454 * 455 * <p>After the client application receives a signal about a successful SDK loading, and has 456 * added a {@link android.view.SurfaceView} to the view hierarchy, it may asynchronously request 457 * a {@link SurfacePackage} to render a view from the SDK. 458 * 459 * <p>When the {@link SurfacePackage} is ready, the {@link OutcomeReceiver#onResult} callback of 460 * the passed {@code receiver} will be invoked. This callback will contain a {@link Bundle} 461 * object, which will contain the key {@link SdkSandboxManager#EXTRA_SURFACE_PACKAGE} whose 462 * associated value is the requested {@link SurfacePackage}. 463 * 464 * <p>The passed {@code params} must contain the following keys: {@link 465 * SdkSandboxManager#EXTRA_WIDTH_IN_PIXELS}, {@link SdkSandboxManager#EXTRA_HEIGHT_IN_PIXELS}, 466 * {@link SdkSandboxManager#EXTRA_DISPLAY_ID} and {@link SdkSandboxManager#EXTRA_HOST_TOKEN}. If 467 * any of these keys are missing or invalid, an {@link IllegalArgumentException} will be thrown. 468 * 469 * <p>This API may only be called while the caller is running in the foreground. Calls from the 470 * background will result in returning RequestSurfacePackageException in the {@code receiver}. 471 * 472 * @param sdkName name of the SDK loaded into the SDK sandbox. 473 * @param params the parameters which the client application passes to the SDK. 474 * @param callbackExecutor the {@link Executor} on which to invoke the callback 475 * @param receiver This either returns a {@link Bundle} on success which will contain the key 476 * {@link SdkSandboxManager#EXTRA_SURFACE_PACKAGE} with a {@link SurfacePackage} value, or 477 * {@link RequestSurfacePackageException} on failure. 478 * @throws IllegalArgumentException if {@code params} does not contain all required keys. 479 * @see android.app.sdksandbox.SdkSandboxManager#EXTRA_WIDTH_IN_PIXELS 480 * @see android.app.sdksandbox.SdkSandboxManager#EXTRA_HEIGHT_IN_PIXELS 481 * @see android.app.sdksandbox.SdkSandboxManager#EXTRA_DISPLAY_ID 482 * @see android.app.sdksandbox.SdkSandboxManager#EXTRA_HOST_TOKEN 483 */ requestSurfacePackage( @onNull String sdkName, @NonNull Bundle params, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver)484 public void requestSurfacePackage( 485 @NonNull String sdkName, 486 @NonNull Bundle params, 487 @NonNull @CallbackExecutor Executor callbackExecutor, 488 @NonNull OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver) { 489 Objects.requireNonNull(sdkName, "sdkName should not be null"); 490 Objects.requireNonNull(params, "params should not be null"); 491 Objects.requireNonNull(callbackExecutor, "callbackExecutor should not be null"); 492 Objects.requireNonNull(receiver, "receiver should not be null"); 493 try { 494 int width = params.getInt(EXTRA_WIDTH_IN_PIXELS, -1); // -1 means invalid width 495 if (width <= 0) { 496 throw new IllegalArgumentException( 497 "Field params should have the entry for the key (" 498 + EXTRA_WIDTH_IN_PIXELS 499 + ") with positive integer value"); 500 } 501 502 int height = params.getInt(EXTRA_HEIGHT_IN_PIXELS, -1); // -1 means invalid height 503 if (height <= 0) { 504 throw new IllegalArgumentException( 505 "Field params should have the entry for the key (" 506 + EXTRA_HEIGHT_IN_PIXELS 507 + ") with positive integer value"); 508 } 509 510 int displayId = params.getInt(EXTRA_DISPLAY_ID, -1); // -1 means invalid displayId 511 if (displayId < 0) { 512 throw new IllegalArgumentException( 513 "Field params should have the entry for the key (" 514 + EXTRA_DISPLAY_ID 515 + ") with integer >= 0"); 516 } 517 518 IBinder hostToken = params.getBinder(EXTRA_HOST_TOKEN); 519 if (hostToken == null) { 520 throw new IllegalArgumentException( 521 "Field params should have the entry for the key (" 522 + EXTRA_HOST_TOKEN 523 + ") with not null IBinder value"); 524 } 525 526 final RequestSurfacePackageReceiverProxy callbackProxy = 527 new RequestSurfacePackageReceiverProxy(callbackExecutor, receiver, mService); 528 529 mService.requestSurfacePackage( 530 mContext.getPackageName(), 531 sdkName, 532 hostToken, 533 displayId, 534 width, 535 height, 536 /*timeAppCalledSystemServer=*/ System.currentTimeMillis(), 537 params, 538 callbackProxy); 539 } catch (RemoteException e) { 540 throw e.rethrowFromSystemServer(); 541 } 542 } 543 544 /** 545 * Starts an {@link Activity} in the SDK sandbox. 546 * 547 * <p>This function will start a new {@link Activity} in the same task of the passed {@code 548 * fromActivity} and pass it to the SDK that shared the passed {@code sdkActivityToken} that 549 * identifies a request from that SDK to stat this {@link Activity}. 550 * 551 * <p>The {@link Activity} will not start in the following cases: 552 * 553 * <ul> 554 * <li>The App calling this API is in the background. 555 * <li>The passed {@code sdkActivityToken} does not map to a request for an {@link Activity} 556 * form the SDK that shared it with the caller app. 557 * <li>The SDK that shared the passed {@code sdkActivityToken} removed its request for this 558 * {@link Activity}. 559 * <li>The sandbox {@link Activity} is already created. 560 * </ul> 561 * 562 * @param fromActivity the {@link Activity} will be used to start the new sandbox {@link 563 * Activity} by calling {@link Activity#startActivity(Intent)} against it. 564 * @param sdkActivityToken the identifier that is shared by the SDK which requests the {@link 565 * Activity}. 566 */ 567 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) startSdkSandboxActivity( @onNull Activity fromActivity, @NonNull IBinder sdkActivityToken)568 public void startSdkSandboxActivity( 569 @NonNull Activity fromActivity, @NonNull IBinder sdkActivityToken) { 570 if (!SdkLevel.isAtLeastU()) { 571 throw new UnsupportedOperationException(); 572 } 573 Intent intent = new Intent(); 574 intent.setAction(ACTION_START_SANDBOXED_ACTIVITY); 575 intent.setPackage(mContext.getPackageManager().getSdkSandboxPackageName()); 576 577 Bundle params = new Bundle(); 578 params.putBinder(EXTRA_SANDBOXED_ACTIVITY_HANDLER, sdkActivityToken); 579 intent.putExtras(params); 580 581 fromActivity.startActivity(intent); 582 } 583 584 /** 585 * A callback for tracking events SDK sandbox death. 586 * 587 * <p>The callback can be added using {@link 588 * SdkSandboxManager#addSdkSandboxProcessDeathCallback(Executor, 589 * SdkSandboxProcessDeathCallback)} and removed using {@link 590 * SdkSandboxManager#removeSdkSandboxProcessDeathCallback(SdkSandboxProcessDeathCallback)} 591 */ 592 public interface SdkSandboxProcessDeathCallback { 593 /** 594 * Notifies the client application that the SDK sandbox has died. The sandbox could die for 595 * various reasons, for example, due to memory pressure on the system, or a crash in the 596 * sandbox. 597 * 598 * The system will automatically restart the sandbox process if it died due to a crash. 599 * However, the state of the sandbox will be lost - so any SDKs that were loaded previously 600 * would have to be loaded again, using {@link SdkSandboxManager#loadSdk(String, Bundle, 601 * Executor, OutcomeReceiver)} to continue using them. 602 */ onSdkSandboxDied()603 void onSdkSandboxDied(); 604 } 605 606 /** @hide */ 607 private static class SdkSandboxProcessDeathCallbackProxy 608 extends ISdkSandboxProcessDeathCallback.Stub { 609 private final Executor mExecutor; 610 public final SdkSandboxProcessDeathCallback callback; 611 SdkSandboxProcessDeathCallbackProxy( Executor executor, SdkSandboxProcessDeathCallback lifecycleCallback)612 SdkSandboxProcessDeathCallbackProxy( 613 Executor executor, SdkSandboxProcessDeathCallback lifecycleCallback) { 614 mExecutor = executor; 615 callback = lifecycleCallback; 616 } 617 618 @Override onSdkSandboxDied()619 public void onSdkSandboxDied() { 620 mExecutor.execute(() -> callback.onSdkSandboxDied()); 621 } 622 } 623 624 /** 625 * Adds keys to set of keys being synced from app's default {@link SharedPreferences} to the SDK 626 * sandbox. 627 * 628 * <p>Synced data will be available for SDKs to read using the {@link 629 * SdkSandboxController#getClientSharedPreferences()} API. 630 * 631 * <p>To stop syncing any key that has been added using this API, use {@link 632 * #removeSyncedSharedPreferencesKeys(Set)}. 633 * 634 * <p>The sync breaks if the app restarts and user must call this API again to rebuild the pool 635 * of keys for syncing. 636 * 637 * <p>Note: This class does not support use across multiple processes. 638 * 639 * @param keys set of keys that will be synced to Sandbox. 640 */ addSyncedSharedPreferencesKeys(@onNull Set<String> keys)641 public void addSyncedSharedPreferencesKeys(@NonNull Set<String> keys) { 642 Objects.requireNonNull(keys, "keys cannot be null"); 643 for (String key : keys) { 644 if (key == null) { 645 throw new IllegalArgumentException("keys cannot contain null"); 646 } 647 } 648 mSyncManager.addSharedPreferencesSyncKeys(keys); 649 } 650 651 /** 652 * Removes keys from set of keys that have been added using {@link 653 * #addSyncedSharedPreferencesKeys(Set)} 654 * 655 * <p>Removed keys will be erased from the SDK sandbox if they have been synced already. 656 * 657 * @param keys set of key names that should no longer be synced to Sandbox. 658 */ removeSyncedSharedPreferencesKeys(@onNull Set<String> keys)659 public void removeSyncedSharedPreferencesKeys(@NonNull Set<String> keys) { 660 for (String key : keys) { 661 if (key == null) { 662 throw new IllegalArgumentException("keys cannot contain null"); 663 } 664 } 665 mSyncManager.removeSharedPreferencesSyncKeys(keys); 666 } 667 668 /** 669 * Returns the set keys that are being synced from app's default {@link SharedPreferences} to 670 * the SDK sandbox. 671 */ 672 @NonNull getSyncedSharedPreferencesKeys()673 public Set<String> getSyncedSharedPreferencesKeys() { 674 return mSyncManager.getSharedPreferencesSyncKeys(); 675 } 676 677 /** @hide */ 678 private static class LoadSdkReceiverProxy extends ILoadSdkCallback.Stub { 679 private final Executor mExecutor; 680 private final OutcomeReceiver<SandboxedSdk, LoadSdkException> mCallback; 681 private final ISdkSandboxManager mService; 682 LoadSdkReceiverProxy( Executor executor, OutcomeReceiver<SandboxedSdk, LoadSdkException> callback, ISdkSandboxManager service)683 LoadSdkReceiverProxy( 684 Executor executor, 685 OutcomeReceiver<SandboxedSdk, LoadSdkException> callback, 686 ISdkSandboxManager service) { 687 mExecutor = executor; 688 mCallback = callback; 689 mService = service; 690 } 691 692 @Override onLoadSdkSuccess(SandboxedSdk sandboxedSdk, long timeSystemServerCalledApp)693 public void onLoadSdkSuccess(SandboxedSdk sandboxedSdk, long timeSystemServerCalledApp) { 694 logLatencyFromSystemServerToApp(timeSystemServerCalledApp); 695 mExecutor.execute(() -> mCallback.onResult(sandboxedSdk)); 696 } 697 698 @Override onLoadSdkFailure(LoadSdkException exception, long timeSystemServerCalledApp)699 public void onLoadSdkFailure(LoadSdkException exception, long timeSystemServerCalledApp) { 700 logLatencyFromSystemServerToApp(timeSystemServerCalledApp); 701 mExecutor.execute(() -> mCallback.onError(exception)); 702 } 703 logLatencyFromSystemServerToApp(long timeSystemServerCalledApp)704 private void logLatencyFromSystemServerToApp(long timeSystemServerCalledApp) { 705 try { 706 mService.logLatencyFromSystemServerToApp( 707 ISdkSandboxManager.LOAD_SDK, 708 // TODO(b/242832156): Add Injector class for testing 709 (int) (System.currentTimeMillis() - timeSystemServerCalledApp)); 710 } catch (RemoteException e) { 711 Log.w( 712 TAG, 713 "Remote exception while calling logLatencyFromSystemServerToApp." 714 + "Error: " 715 + e.getMessage()); 716 } 717 } 718 } 719 720 /** @hide */ 721 private static class RequestSurfacePackageReceiverProxy 722 extends IRequestSurfacePackageCallback.Stub { 723 private final Executor mExecutor; 724 private final OutcomeReceiver<Bundle, RequestSurfacePackageException> mReceiver; 725 private final ISdkSandboxManager mService; 726 RequestSurfacePackageReceiverProxy( Executor executor, OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver, ISdkSandboxManager service)727 RequestSurfacePackageReceiverProxy( 728 Executor executor, 729 OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver, 730 ISdkSandboxManager service) { 731 mExecutor = executor; 732 mReceiver = receiver; 733 mService = service; 734 } 735 736 @Override onSurfacePackageReady( SurfacePackage surfacePackage, int surfacePackageId, Bundle params, long timeSystemServerCalledApp)737 public void onSurfacePackageReady( 738 SurfacePackage surfacePackage, 739 int surfacePackageId, 740 Bundle params, 741 long timeSystemServerCalledApp) { 742 logLatencyFromSystemServerToApp(timeSystemServerCalledApp); 743 mExecutor.execute( 744 () -> { 745 params.putParcelable(EXTRA_SURFACE_PACKAGE, surfacePackage); 746 mReceiver.onResult(params); 747 }); 748 } 749 750 @Override onSurfacePackageError( int errorCode, String errorMsg, long timeSystemServerCalledApp)751 public void onSurfacePackageError( 752 int errorCode, String errorMsg, long timeSystemServerCalledApp) { 753 logLatencyFromSystemServerToApp(timeSystemServerCalledApp); 754 mExecutor.execute( 755 () -> 756 mReceiver.onError( 757 new RequestSurfacePackageException(errorCode, errorMsg))); 758 } 759 logLatencyFromSystemServerToApp(long timeSystemServerCalledApp)760 private void logLatencyFromSystemServerToApp(long timeSystemServerCalledApp) { 761 try { 762 mService.logLatencyFromSystemServerToApp( 763 ISdkSandboxManager.REQUEST_SURFACE_PACKAGE, 764 // TODO(b/242832156): Add Injector class for testing 765 (int) (System.currentTimeMillis() - timeSystemServerCalledApp)); 766 } catch (RemoteException e) { 767 Log.w( 768 TAG, 769 "Remote exception while calling logLatencyFromSystemServerToApp." 770 + "Error: " 771 + e.getMessage()); 772 } 773 } 774 } 775 776 /** 777 * Return the AdServicesManager 778 * 779 * @hide 780 */ getAdServicesManager()781 public IBinder getAdServicesManager() { 782 try { 783 return mService.getAdServicesManager(); 784 } catch (RemoteException e) { 785 throw e.rethrowFromSystemServer(); 786 } 787 } 788 } 789