summaryrefslogtreecommitdiff
path: root/sdksandbox/framework/java/android/app/sdksandbox/SdkSandboxManager.java
blob: cb37402a13911b3c1f44392a53beccca54c31665 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app.sdksandbox;

import static android.app.sdksandbox.SdkSandboxManager.SDK_SANDBOX_SERVICE;

import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.SharedLibraryInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.util.Log;
import android.view.SurfaceControlViewHost.SurfacePackage;

import com.android.internal.annotations.GuardedBy;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;

/**
 * Provides APIs to load {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE SDKs}
 * into SDK sandbox process, and then interact with them.
 *
 * <p>{@code SdkSandbox} is a java process running in a separate uid range. Each app has its own
 * SDK sandbox process.
 *
 * <p>First app needs to declare {@code SDKs} it depends on in it's {@code AndroidManifest.xml}
 * using {@code <uses-sdk-library>} tag. App can only load {@code SDKs} it depends on into the
 * {@code SdkSandbox}.
 *
 * <p>Note: All APIs defined in this class are not stable and subject to change.
 *
 * @see android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE
 * @see
 * <a href="https://developer.android.com/design-for-safety/ads/sdk-runtime">SDK runtime design proposal</a>
 */
@SystemService(SDK_SANDBOX_SERVICE)
public final class SdkSandboxManager {

    /**
     * Use with {@link Context#getSystemService(String)} to retrieve a {@link SdkSandboxManager} for
     * interacting with the SDKs belonging to this client application.
     */
    public static final String SDK_SANDBOX_SERVICE = "sdk_sandbox";

    /**
     * Sdk sandbox process is not available.
     *
     * <p>This indicates that the sdk sandbox process is not available, either because it has died,
     * disconnected or was not created in the first place.
     */
    public static final int SDK_SANDBOX_PROCESS_NOT_AVAILABLE = 503;

    /**
     * SDK not found.
     *
     * <p>This indicates that client application tried to load a non-existing SDK by calling {@link
     * SdkSandboxManager#loadSdk(String, Bundle, Executor, OutcomeReceiver)}.
     */
    public static final int LOAD_SDK_NOT_FOUND = 100;

    /**
     * SDK is already loaded.
     *
     * <p>This indicates that client application tried to reload the same SDk by calling {@link
     * SdkSandboxManager#loadSdk(String, Bundle, Executor, OutcomeReceiver)} after being
     * successfully loaded.
     */
    public static final int LOAD_SDK_ALREADY_LOADED = 101;

    /**
     * SDK error after being loaded.
     *
     * <p>This indicates that the SDK encountered an error during post-load initialization. The
     * details of this can be obtained from the Bundle returned in {@link LoadSdkException} through
     * the {@link OutcomeReceiver} passed in to {@link SdkSandboxManager#loadSdk}.
     */
    public static final int LOAD_SDK_SDK_DEFINED_ERROR = 102;

    /**
     * SDK sandbox is disabled.
     *
     * <p>This indicates that the SDK sandbox is disabled. Any subsequent attempts to load SDKs in
     * this boot will also fail.
     */
    public static final int LOAD_SDK_SDK_SANDBOX_DISABLED = 103;

    /** Internal error while loading SDK.
     *
     * <p>This indicates a generic internal error happened while applying the call from
     * client application.
     */
    public static final int LOAD_SDK_INTERNAL_ERROR = 500;

    private static final String TAG = "SdkSandboxManager";

    /** @hide */
    @IntDef(
            value = {
                LOAD_SDK_NOT_FOUND,
                LOAD_SDK_ALREADY_LOADED,
                LOAD_SDK_SDK_DEFINED_ERROR,
                LOAD_SDK_SDK_SANDBOX_DISABLED,
                LOAD_SDK_INTERNAL_ERROR,
                SDK_SANDBOX_PROCESS_NOT_AVAILABLE
            })
    @Retention(RetentionPolicy.SOURCE)
    public @interface LoadSdkErrorCode {}

    /** Internal error while requesting a {@link SurfacePackage}.
     *
     * <p>This indicates a generic internal error happened while requesting a
     * {@link SurfacePackage}.
     */
    public static final int REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR = 700;

    /** @hide */
    @IntDef(value = {REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR, SDK_SANDBOX_PROCESS_NOT_AVAILABLE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface RequestSurfacePackageErrorCode {}


    /**
     * SDK Sandbox is disabled.
     *
     * <p>{@link SdkSandboxManager} APIs are hidden. Attempts at calling them will result in
     * {@link UnsupportedOperationException}.
     */
    public static final int SDK_SANDBOX_STATE_DISABLED = 0;

    /**
     * SDK Sandbox is enabled.
     *
     * <p>App can use {@link SdkSandboxManager} APIs to load {@code SDKs} it depends on into the
     * corresponding {@code SdkSandbox} process.
     */
    public static final int SDK_SANDBOX_STATE_ENABLED_PROCESS_ISOLATION = 2;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = "SDK_SANDBOX_STATUS_", value = {
            SDK_SANDBOX_STATE_DISABLED,
            SDK_SANDBOX_STATE_ENABLED_PROCESS_ISOLATION,
    })
    public @interface SdkSandboxState {}

    /**
     * The name of key to be used in the Bundle fields of {@link #requestSurfacePackage(String,
     * Bundle, Executor, OutcomeReceiver)}, its value should define the integer width of the {@link
     * SurfacePackage} in pixels.
     */
    public static final String EXTRA_WIDTH_IN_PIXELS =
            "android.app.sdksandbox.extra.WIDTH_IN_PIXELS";
    /**
     * The name of key to be used in the Bundle fields of {@link #requestSurfacePackage(String,
     * Bundle, Executor, OutcomeReceiver)}, its value should define the integer height of the {@link
     * SurfacePackage} in pixels.
     */
    public static final String EXTRA_HEIGHT_IN_PIXELS =
            "android.app.sdksandbox.extra.HEIGHT_IN_PIXELS";
    /**
     * The name of key to be used in the Bundle fields of {@link #requestSurfacePackage(String,
     * Bundle, Executor, OutcomeReceiver)}, its value should define the integer ID of the logical
     * display to display the {@link SurfacePackage}.
     */
    public static final String EXTRA_DISPLAY_ID = "android.app.sdksandbox.extra.DISPLAY_ID";

    /**
     * The name of key to be used in the Bundle fields of {@link #requestSurfacePackage(String,
     * Bundle, Executor, OutcomeReceiver)}, its value should present the token returned by {@link
     * android.view.SurfaceView#getHostToken()} once the {@link android.view.SurfaceView} has been
     * added to the view hierarchy. Only a non-null value is accepted to enable ANR reporting.
     */
    public static final String EXTRA_HOST_TOKEN = "android.app.sdksandbox.extra.HOST_TOKEN";

    /**
     * The name of key in the Bundle which is passed to the {@code onResult} function of the {@link
     * OutcomeReceiver} which is field of {@link #requestSurfacePackage(String, Bundle, Executor,
     * OutcomeReceiver)}, its value presents the requested {@link SurfacePackage}.
     */
    public static final String EXTRA_SURFACE_PACKAGE =
            "android.app.sdksandbox.extra.SURFACE_PACKAGE";

    private final ISdkSandboxManager mService;
    private final Context mContext;

    @GuardedBy("mLifecycleCallbacks")
    private final ArrayList<SdkSandboxProcessDeathCallbackProxy> mLifecycleCallbacks =
            new ArrayList<>();

    private final SharedPreferencesSyncManager mSyncManager;

    /** @hide */
    public SdkSandboxManager(@NonNull Context context, @NonNull ISdkSandboxManager binder) {
        mContext = Objects.requireNonNull(context, "context should not be null");
        mService = Objects.requireNonNull(binder, "binder should not be null");
        // TODO(b/239403323): There can be multiple package in the same app process
        mSyncManager = SharedPreferencesSyncManager.getInstance(context, binder);
    }

    /**
     * Returns current state of the {@code SdkSandbox}.
     */
    @SdkSandboxState
    public static int getSdkSandboxState() {
        return SDK_SANDBOX_STATE_ENABLED_PROCESS_ISOLATION;
    }

    /**
     * Stop the SDK sandbox process corresponding to the app.
     *
     * @hide
     */
    @TestApi
    @RequiresPermission("com.android.app.sdksandbox.permission.STOP_SDK_SANDBOX")
    public void stopSdkSandbox() {
        try {
            mService.stopSdkSandbox(mContext.getPackageName());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Add a callback which gets registered for sdk sandbox lifecycle events, such as sdk sandbox
     * death. If the sandbox has not yet been created when this is called, the request will be
     * stored until a sandbox is created, at which point it is activated for that sandbox. Multiple
     * callbacks can be added to detect death.
     *
     * @param callbackExecutor the {@link Executor} on which to invoke the callback
     * @param callback the {@link SdkSandboxProcessDeathCallback} which will receive sdk sandbox
     *     lifecycle events.
     */
    public void addSdkSandboxProcessDeathCallback(
            @NonNull @CallbackExecutor Executor callbackExecutor,
            @NonNull SdkSandboxProcessDeathCallback callback) {
        Objects.requireNonNull(callbackExecutor, "callbackExecutor should not be null");
        Objects.requireNonNull(callback, "callback should not be null");

        synchronized (mLifecycleCallbacks) {
            final SdkSandboxProcessDeathCallbackProxy callbackProxy =
                    new SdkSandboxProcessDeathCallbackProxy(callbackExecutor, callback);
            try {
                mService.addSdkSandboxProcessDeathCallback(
                        mContext.getPackageName(),
                        /*timeAppCalledSystemServer=*/ System.currentTimeMillis(),
                        callbackProxy);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            mLifecycleCallbacks.add(callbackProxy);
        }
    }

    /**
     * Remove an {@link SdkSandboxProcessDeathCallback} that was previously added using {@link
     * SdkSandboxManager#addSdkSandboxProcessDeathCallback(Executor,
     * SdkSandboxProcessDeathCallback)}
     *
     * @param callback the {@link SdkSandboxProcessDeathCallback} which was previously added using
     *     {@link SdkSandboxManager#addSdkSandboxProcessDeathCallback(Executor,
     *     SdkSandboxProcessDeathCallback)}
     */
    public void removeSdkSandboxProcessDeathCallback(
            @NonNull SdkSandboxProcessDeathCallback callback) {
        Objects.requireNonNull(callback, "callback should not be null");
        synchronized (mLifecycleCallbacks) {
            for (int i = mLifecycleCallbacks.size() - 1; i >= 0; i--) {
                final SdkSandboxProcessDeathCallbackProxy callbackProxy =
                        mLifecycleCallbacks.get(i);
                if (callbackProxy.callback == callback) {
                    try {
                        mService.removeSdkSandboxProcessDeathCallback(
                                mContext.getPackageName(),
                                /*timeAppCalledSystemServer=*/ System.currentTimeMillis(),
                                callbackProxy);
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                    mLifecycleCallbacks.remove(i);
                }
            }
        }
    }

    /**
     * Load SDK in a SDK sandbox java process.
     *
     * <p>It loads SDK library with {@code sdkName} to a sandbox process asynchronously, caller
     * should be notified through {@code receiver}.
     *
     * <p>App should already declare {@code SDKs} it depends on in its {@code AndroidManifest} using
     * {@code <use-sdk-library>} tag. App can only load {@code SDKs} it depends on into the {@code
     * SdkSandbox}.
     *
     * <p>When client application loads the first SDK, a new {@code SdkSandbox} process will be
     * created, otherwise other SDKs will be loaded into the same sandbox which already created for
     * the client application.
     *
     * <p>This API may only be called while the caller is running in the foreground. Calls from the
     * background will result in a {@link SecurityException} being thrown.
     *
     * @param sdkName name of the SDK to be loaded.
     * @param params additional parameters to be passed to the SDK in the form of a {@link Bundle}
     *     as agreed between the client and the SDK.
     * @param executor the {@link Executor} on which to invoke the receiver.
     * @param receiver This either returns a {@link SandboxedSdk} on a successful run, or {@link
     *     LoadSdkException}.
     */
    public void loadSdk(
            @NonNull String sdkName,
            @NonNull Bundle params,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull OutcomeReceiver<SandboxedSdk, LoadSdkException> receiver) {
        Objects.requireNonNull(sdkName, "sdkName should not be null");
        Objects.requireNonNull(params, "params should not be null");
        Objects.requireNonNull(executor, "executor should not be null");
        Objects.requireNonNull(receiver, "receiver should not be null");
        final LoadSdkReceiverProxy callbackProxy =
                new LoadSdkReceiverProxy(executor, receiver, mService);
        try {
            mService.loadSdk(
                    mContext.getPackageName(),
                    sdkName,
                    /*timeAppCalledSystemServer=*/ System.currentTimeMillis(),
                    params,
                    callbackProxy);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Fetches information about Sdks that are loaded in the sandbox.
     *
     * @return List of {@link SharedLibraryInfo} containing all currently loaded sdks
     * @hide
     */
    public @NonNull List<SharedLibraryInfo> getLoadedSdkLibrariesInfo() {
        try {
            return mService.getLoadedSdkLibrariesInfo(
                    mContext.getPackageName(),
                    /*timeAppCalledSystemServer=*/ System.currentTimeMillis());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Unloads an SDK that has been previously loaded by the caller.
     *
     * <p>It is not guaranteed that the memory allocated for this SDK will be freed immediately. All
     * subsequent calls to {@link #requestSurfacePackage(String, Bundle, Executor, OutcomeReceiver)}
     * for the given {@code sdkName} will fail.
     *
     * <p>This API may only be called while the caller is running in the foreground. Calls from the
     * background will result in a {@link SecurityException} being thrown.
     *
     * @param sdkName name of the SDK to be unloaded.
     * @throws IllegalArgumentException if the SDK is not loaded.
     */
    public void unloadSdk(@NonNull String sdkName) {
        Objects.requireNonNull(sdkName, "sdkName should not be null");
        try {
            mService.unloadSdk(
                    mContext.getPackageName(),
                    sdkName,
                    /*timeAppCalledSystemServer=*/ System.currentTimeMillis());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Send a request for a surface package to the sdk.
     *
     * <p>After client application receives a signal about a successful SDK loading, and has added a
     * {@link android.view.SurfaceView} to the view hierarchy, it may asynchronously request a
     * {@link SurfacePackage} to render a view from the SDK.
     *
     * <p>When the {@link SurfacePackage} is ready, {@code onResult} function of the {@code
     * receiver} will be called with Bundle, that bundle will contain the key {@code
     * EXTRA_SURFACE_PACKAGE} with value present the requested {@link SurfacePackage}.
     *
     * <p>This API may only be called while the caller is running in the foreground. Calls from the
     * background will result in a {@link SecurityException} being thrown.
     *
     * @param sdkName name of the SDK loaded into sdk sandbox.
     * @param params the parameters which the client application passes to the SDK, it should
     *     contain the following params: (EXTRA_WIDTH_IN_PIXELS, EXTRA_HEIGHT_IN_PIXELS,
     *     EXTRA_DISPLAY_ID, EXTRA_HOST_TOKEN). If any of these params is missing, an
     *     IllegalArgumentException will be thrown. Any additional parameters may be passed as
     *     agreed between the client and the SDK.
     * @param callbackExecutor the {@link Executor} on which to invoke the callback
     * @param receiver This either returns a {@link Bundle} on success which should contain the key
     *     EXTRA_SURFACE_PACKAGE with value of {@link SurfacePackage} response, or {@link
     *     RequestSurfacePackageException} on failure.
     * @throws IllegalArgumentException if any of the following params (EXTRA_WIDTH_IN_PIXELS,
     *     EXTRA_HEIGHT_IN_PIXELS, EXTRA_DISPLAY_ID, EXTRA_HOST_TOKEN) are missing from the Bundle
     *     or passed with the wrong value or type.
     * @see android.app.sdksandbox.SdkSandboxManager#EXTRA_WIDTH_IN_PIXELS
     * @see android.app.sdksandbox.SdkSandboxManager#EXTRA_HEIGHT_IN_PIXELS
     * @see android.app.sdksandbox.SdkSandboxManager#EXTRA_DISPLAY_ID
     * @see android.app.sdksandbox.SdkSandboxManager#EXTRA_HOST_TOKEN
     */
    public void requestSurfacePackage(
            @NonNull String sdkName,
            @NonNull Bundle params,
            @NonNull @CallbackExecutor Executor callbackExecutor,
            @NonNull OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver) {
        Objects.requireNonNull(sdkName, "sdkName should not be null");
        Objects.requireNonNull(params, "params should not be null");
        Objects.requireNonNull(callbackExecutor, "callbackExecutor should not be null");
        Objects.requireNonNull(receiver, "receiver should not be null");
        try {
            int width = params.getInt(EXTRA_WIDTH_IN_PIXELS, -1); // -1 means invalid width
            if (width <= 0) {
                throw new IllegalArgumentException(
                        "Field params should have the entry for the key ("
                                + EXTRA_WIDTH_IN_PIXELS
                                + ") with positive integer value");
            }

            int height = params.getInt(EXTRA_HEIGHT_IN_PIXELS, -1); // -1 means invalid height
            if (height <= 0) {
                throw new IllegalArgumentException(
                        "Field params should have the entry for the key ("
                                + EXTRA_HEIGHT_IN_PIXELS
                                + ") with positive integer value");
            }

            int displayId = params.getInt(EXTRA_DISPLAY_ID, -1); // -1 means invalid displayId
            if (displayId < 0) {
                throw new IllegalArgumentException(
                        "Field params should have the entry for the key ("
                                + EXTRA_DISPLAY_ID
                                + ") with integer >= 0");
            }

            IBinder hostToken = params.getBinder(EXTRA_HOST_TOKEN);
            if (hostToken == null) {
                throw new IllegalArgumentException(
                        "Field params should have the entry for the key ("
                                + EXTRA_HOST_TOKEN
                                + ") with not null IBinder value");
            }

            final RequestSurfacePackageReceiverProxy callbackProxy =
                    new RequestSurfacePackageReceiverProxy(callbackExecutor, receiver, mService);

            mService.requestSurfacePackage(
                    mContext.getPackageName(),
                    sdkName,
                    hostToken,
                    displayId,
                    width,
                    height,
                    /*timeAppCalledSystemServer=*/ System.currentTimeMillis(),
                    params,
                    callbackProxy);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * A callback for tracking events SDK sandbox death.
     *
     * <p>The callback can be added using {@link
     * SdkSandboxManager#addSdkSandboxProcessDeathCallback(Executor,
     * SdkSandboxProcessDeathCallback)} and removed using {@link
     * SdkSandboxManager#removeSdkSandboxProcessDeathCallback(SdkSandboxProcessDeathCallback)}
     */
    public interface SdkSandboxProcessDeathCallback {
        /**
         * Notifies the client application that the SDK sandbox has died. The sandbox could die for
         * various reasons, for example, due to memory pressure on the system, or a crash in the
         * sandbox.
         *
         * The system will automatically restart the sandbox process if it died due to a crash.
         * However, the state of the sandbox will be lost - so any SDKs that were loaded previously
         * would have to be loaded again, using {@link SdkSandboxManager#loadSdk(String, Bundle,
         * Executor, OutcomeReceiver)} to continue using them.
         */
        void onSdkSandboxDied();
    }

    /** @hide */
    private static class SdkSandboxProcessDeathCallbackProxy
            extends ISdkSandboxProcessDeathCallback.Stub {
        private final Executor mExecutor;
        public final SdkSandboxProcessDeathCallback callback;

        SdkSandboxProcessDeathCallbackProxy(
                Executor executor, SdkSandboxProcessDeathCallback lifecycleCallback) {
            mExecutor = executor;
            callback = lifecycleCallback;
        }

        @Override
        public void onSdkSandboxDied() {
            mExecutor.execute(() -> callback.onSdkSandboxDied());
        }
    }

    // TODO(b/237410689): Update links to getClientSharedPreferences when cl is merged.
    /**
     * Adds keys to set of keys being synced from app's default {@link SharedPreferences} to
     * SdkSandbox.
     *
     * <p>Synced data will be available for sdks to read using the {@code
     * getClientSharedPreferences} API.
     *
     * <p>To stop syncing any key that has been added using this API, use {@link
     * #removeSyncedSharedPreferencesKeys(Set)}.
     *
     * <p>The sync breaks if the app restarts and user must call this API again to rebuild the pool
     * of keys for syncing.
     *
     * <p>Note: This class does not support use across multiple processes.
     *
     * @param keys set of keys that will be synced to Sandbox.
     */
    public void addSyncedSharedPreferencesKeys(@NonNull Set<String> keys) {
        Objects.requireNonNull(keys, "keys cannot be null");
        for (String key : keys) {
            if (key == null) {
                throw new IllegalArgumentException("keys cannot contain null");
            }
        }
        mSyncManager.addSharedPreferencesSyncKeys(keys);
    }

    /**
     * Removes keys from set of keys that have been added using {@link
     * #addSyncedSharedPreferencesKeys(Set)}
     *
     * <p>Removed keys will be erased from SdkSandbox if they have been synced already.
     *
     * @param keys set of key names that should no longer be synced to Sandbox.
     */
    public void removeSyncedSharedPreferencesKeys(@NonNull Set<String> keys) {
        for (String key : keys) {
            if (key == null) {
                throw new IllegalArgumentException("keys cannot contain null");
            }
        }
        mSyncManager.removeSharedPreferencesSyncKeys(keys);
    }

    /**
     * Returns the set keys that are being synced from app's default {@link SharedPreferences} to
     * SdkSandbox.
     */
    @NonNull
    public Set<String> getSyncedSharedPreferencesKeys() {
        return mSyncManager.getSharedPreferencesSyncKeys();
    }

    /** @hide */
    private static class LoadSdkReceiverProxy extends ILoadSdkCallback.Stub {
        private final Executor mExecutor;
        private final OutcomeReceiver<SandboxedSdk, LoadSdkException> mCallback;
        private final ISdkSandboxManager mService;

        LoadSdkReceiverProxy(
                Executor executor,
                OutcomeReceiver<SandboxedSdk, LoadSdkException> callback,
                ISdkSandboxManager service) {
            mExecutor = executor;
            mCallback = callback;
            mService = service;
        }

        @Override
        public void onLoadSdkSuccess(SandboxedSdk sandboxedSdk, long timeSystemServerCalledApp) {
            logLatencyFromSystemServerToApp(timeSystemServerCalledApp);
            mExecutor.execute(() -> mCallback.onResult(sandboxedSdk));
        }

        @Override
        public void onLoadSdkFailure(LoadSdkException exception, long timeSystemServerCalledApp) {
            logLatencyFromSystemServerToApp(timeSystemServerCalledApp);
            mExecutor.execute(() -> mCallback.onError(exception));
        }

        private void logLatencyFromSystemServerToApp(long timeSystemServerCalledApp) {
            try {
                mService.logLatencyFromSystemServerToApp(
                        ISdkSandboxManager.LOAD_SDK,
                        // TODO(b/242832156): Add Injector class for testing
                        (int) (System.currentTimeMillis() - timeSystemServerCalledApp));
            } catch (RemoteException e) {
                Log.w(
                        TAG,
                        "Remote exception while calling logLatencyFromSystemServerToApp."
                                + "Error: "
                                + e.getMessage());
            }
        }
    }

    /** @hide */
    private static class RequestSurfacePackageReceiverProxy
            extends IRequestSurfacePackageCallback.Stub {
        private final Executor mExecutor;
        private final OutcomeReceiver<Bundle, RequestSurfacePackageException> mReceiver;
        private final ISdkSandboxManager mService;

        RequestSurfacePackageReceiverProxy(
                Executor executor,
                OutcomeReceiver<Bundle, RequestSurfacePackageException> receiver,
                ISdkSandboxManager service) {
            mExecutor = executor;
            mReceiver = receiver;
            mService = service;
        }

        @Override
        public void onSurfacePackageReady(
                SurfacePackage surfacePackage,
                int surfacePackageId,
                Bundle params,
                long timeSystemServerCalledApp) {
            logLatencyFromSystemServerToApp(timeSystemServerCalledApp);
            mExecutor.execute(
                    () -> {
                        params.putParcelable(EXTRA_SURFACE_PACKAGE, surfacePackage);
                        mReceiver.onResult(params);
                    });
        }

        @Override
        public void onSurfacePackageError(
                int errorCode, String errorMsg, long timeSystemServerCalledApp) {
            logLatencyFromSystemServerToApp(timeSystemServerCalledApp);
            mExecutor.execute(
                    () ->
                            mReceiver.onError(
                                    new RequestSurfacePackageException(errorCode, errorMsg)));
        }

        private void logLatencyFromSystemServerToApp(long timeSystemServerCalledApp) {
            try {
                mService.logLatencyFromSystemServerToApp(
                        ISdkSandboxManager.REQUEST_SURFACE_PACKAGE,
                        // TODO(b/242832156): Add Injector class for testing
                        (int) (System.currentTimeMillis() - timeSystemServerCalledApp));
            } catch (RemoteException e) {
                Log.w(
                        TAG,
                        "Remote exception while calling logLatencyFromSystemServerToApp."
                                + "Error: "
                                + e.getMessage());
            }
        }
    }
}