• 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.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