/* * 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.SandboxLatencyInfo.RESULT_CODE_LOAD_SDK_ALREADY_LOADED; import static android.app.sdksandbox.SandboxLatencyInfo.RESULT_CODE_LOAD_SDK_INTERNAL_ERROR; import static android.app.sdksandbox.SandboxLatencyInfo.RESULT_CODE_LOAD_SDK_NOT_FOUND; import static android.app.sdksandbox.SandboxLatencyInfo.RESULT_CODE_LOAD_SDK_SDK_DEFINED_ERROR; import static android.app.sdksandbox.SandboxLatencyInfo.RESULT_CODE_LOAD_SDK_SDK_SANDBOX_DISABLED; import static android.app.sdksandbox.SandboxLatencyInfo.RESULT_CODE_SDK_SANDBOX_PROCESS_NOT_AVAILABLE; import static android.app.sdksandbox.SandboxLatencyInfo.RESULT_CODE_UNSPECIFIED; 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.SdkConstant; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.Activity; import android.app.sdksandbox.sdkprovider.SdkSandboxActivityHandler; import android.app.sdksandbox.sdkprovider.SdkSandboxController; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.OutcomeReceiver; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.view.SurfaceControlViewHost.SurfacePackage; import androidx.annotation.RequiresApi; import com.android.internal.annotations.GuardedBy; import com.android.modules.utils.build.SdkLevel; 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 the * SDK sandbox process, and then interact with them. * *

SDK sandbox is a java process running in a separate uid range. Each app may have its own SDK * sandbox process. * *

The app first needs to declare SDKs it depends on in its manifest using the {@code * } tag. Apps may only load SDKs they depend on into the SDK sandbox. * * @see android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE * @see SDK Runtime design * proposal */ @SystemService(SDK_SANDBOX_SERVICE) public final class SdkSandboxManager { /** * Use with {@link Context#getSystemService(String)} to retrieve an {@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. * *

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

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

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

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

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

This indicates a generic internal error happened while applying the call from client * application. */ public static final int LOAD_SDK_INTERNAL_ERROR = 500; /** * Action name for the intent which starts {@link Activity} in SDK sandbox. * *

System services would know if the intent is created to start {@link Activity} in sandbox * by comparing the action of the intent to the value of this field. * *

This intent should contain an extra param with key equals to {@link * #EXTRA_SANDBOXED_ACTIVITY_HANDLER} and value equals to the {@link IBinder} that identifies * the {@link SdkSandboxActivityHandler} that registered before by an SDK. If the extra param is * missing, the {@link Activity} will fail to start. * * @hide */ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String ACTION_START_SANDBOXED_ACTIVITY = "android.app.sdksandbox.action.START_SANDBOXED_ACTIVITY"; /** * The key for an element in {@link Activity} intent extra params, the value is an {@link * SdkSandboxActivityHandler} registered by an SDK. * * @hide */ public static final String EXTRA_SANDBOXED_ACTIVITY_HANDLER = "android.app.sdksandbox.extra.SANDBOXED_ACTIVITY_HANDLER"; /** * The key for an element in {@link Activity} intent extra params, the value is set while * calling {@link #startSdkSandboxActivity(Activity, IBinder)}. * * @hide */ public static final String EXTRA_SANDBOXED_ACTIVITY_INITIATION_TIME = "android.app.sdksandbox.extra.EXTRA_SANDBOXED_ACTIVITY_INITIATION_TIME"; private static final String TAG = "SdkSandboxManager"; private TimeProvider mTimeProvider; static class TimeProvider { long elapsedRealtime() { return SystemClock.elapsedRealtime(); } } /** @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}. * *

This indicates a generic internal error happened while requesting a * {@link SurfacePackage}. */ public static final int REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR = 700; /** * SDK is not loaded while requesting a {@link SurfacePackage}. * *

This indicates that the SDK for which the {@link SurfacePackage} is being requested is not * loaded, either because the sandbox died or because it was not loaded in the first place. */ public static final int REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED = 701; /** @hide */ @IntDef( prefix = "REQUEST_SURFACE_PACKAGE_", value = { REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR, REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED }) @Retention(RetentionPolicy.SOURCE) public @interface RequestSurfacePackageErrorCode {} /** * SDK sandbox is disabled. * *

{@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. * *

App can use {@link SdkSandboxManager} APIs to load {@code SDKs} it depends on into the * corresponding SDK sandbox 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. * * @deprecated Parameter for {@link #requestSurfacePackage(String, Bundle, Executor, * OutcomeReceiver)} which is getting deprecated. */ @Deprecated 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. * * @deprecated Parameter for {@link #requestSurfacePackage(String, Bundle, Executor, * OutcomeReceiver)} which is getting deprecated. */ @Deprecated 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}. * * @deprecated Parameter for {@link #requestSurfacePackage(String, Bundle, Executor, * OutcomeReceiver)} which is getting deprecated. */ @Deprecated 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. * * @deprecated Parameter for {@link #requestSurfacePackage(String, Bundle, Executor, * OutcomeReceiver)} which is getting deprecated. */ @Deprecated 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}. * * @deprecated Parameter for {@link #requestSurfacePackage(String, Bundle, Executor, * OutcomeReceiver)} which is getting deprecated. */ @Deprecated 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 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); mTimeProvider = new TimeProvider(); } /** Returns the current state of the availability of the SDK sandbox feature. */ @SdkSandboxState public static int getSdkSandboxState() { return SDK_SANDBOX_STATE_ENABLED_PROCESS_ISOLATION; } /** * Returns if SDK sandbox process corresponding to the app currently running. * * @hide */ @TestApi public boolean isSdkSandboxServiceRunning() { try { return mService.isSdkSandboxServiceRunning(mContext.getPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Stops 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(); } } /** * Adds 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 and will not be removed when the sandbox dies. * * @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); SandboxLatencyInfo sandboxLatencyInfo = new SandboxLatencyInfo( SandboxLatencyInfo.METHOD_ADD_SDK_SANDBOX_LIFECYCLE_CALLBACK); sandboxLatencyInfo.setTimeAppCalledSystemServer(mTimeProvider.elapsedRealtime()); try { mService.addSdkSandboxProcessDeathCallback( mContext.getPackageName(), sandboxLatencyInfo, callbackProxy); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } mLifecycleCallbacks.add(callbackProxy); } } /** * Removes 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) { SandboxLatencyInfo sandboxLatencyInfo = new SandboxLatencyInfo( SandboxLatencyInfo .METHOD_REMOVE_SDK_SANDBOX_LIFECYCLE_CALLBACK); sandboxLatencyInfo.setTimeAppCalledSystemServer( mTimeProvider.elapsedRealtime()); try { mService.removeSdkSandboxProcessDeathCallback( mContext.getPackageName(), sandboxLatencyInfo, callbackProxy); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } mLifecycleCallbacks.remove(i); } } } } /** * Registers {@link AppOwnedSdkSandboxInterface} for an app process. * *

Registering an {@link AppOwnedSdkSandboxInterface} that has same name as a previously * registered interface will result in {@link IllegalStateException}. * *

{@link AppOwnedSdkSandboxInterface#getName()} refers to the name of the interface. * * @param appOwnedSdkSandboxInterface the AppOwnedSdkSandboxInterface to be registered */ public void registerAppOwnedSdkSandboxInterface( @NonNull AppOwnedSdkSandboxInterface appOwnedSdkSandboxInterface) { SandboxLatencyInfo sandboxLatencyInfo = new SandboxLatencyInfo( SandboxLatencyInfo.METHOD_REGISTER_APP_OWNED_SDK_SANDBOX_INTERFACE); sandboxLatencyInfo.setTimeAppCalledSystemServer(mTimeProvider.elapsedRealtime()); try { mService.registerAppOwnedSdkSandboxInterface( mContext.getPackageName(), appOwnedSdkSandboxInterface, sandboxLatencyInfo); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Unregisters {@link AppOwnedSdkSandboxInterface}s for an app process. * * @param name the name under which AppOwnedSdkSandboxInterface was registered. */ public void unregisterAppOwnedSdkSandboxInterface(@NonNull String name) { SandboxLatencyInfo sandboxLatencyInfo = new SandboxLatencyInfo( SandboxLatencyInfo.METHOD_UNREGISTER_APP_OWNED_SDK_SANDBOX_INTERFACE); sandboxLatencyInfo.setTimeAppCalledSystemServer(mTimeProvider.elapsedRealtime()); try { mService.unregisterAppOwnedSdkSandboxInterface( mContext.getPackageName(), name, sandboxLatencyInfo); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Fetches a list of {@link AppOwnedSdkSandboxInterface} registered for an app * * @return empty list if callingInfo not found in map otherwise a list of {@link * AppOwnedSdkSandboxInterface} */ public @NonNull List getAppOwnedSdkSandboxInterfaces() { SandboxLatencyInfo sandboxLatencyInfo = new SandboxLatencyInfo( SandboxLatencyInfo.METHOD_GET_APP_OWNED_SDK_SANDBOX_INTERFACES); sandboxLatencyInfo.setTimeAppCalledSystemServer(mTimeProvider.elapsedRealtime()); try { return mService.getAppOwnedSdkSandboxInterfaces( mContext.getPackageName(), sandboxLatencyInfo); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Loads SDK in an SDK sandbox java process. * *

Loads SDK library with {@code sdkName} to an SDK sandbox process asynchronously. The * caller will be notified through the {@code receiver}. * *

The caller should already declare {@code SDKs} it depends on in its manifest using {@code * } tag. The caller may only load {@code SDKs} it depends on into the SDK * sandbox. * *

When the client application loads the first SDK, a new SDK sandbox process will be * created. If a sandbox has already been created for the client application, additional SDKs * will be loaded into the same sandbox. * *

This API may only be called while the caller is running in the foreground. Calls from the * background will result in returning {@link LoadSdkException} in the {@code receiver}. * * @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 receives 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 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); IBinder appProcessToken; // Context.getProcessToken() only exists on U+. if (SdkLevel.isAtLeastU()) { appProcessToken = mContext.getProcessToken(); } else { appProcessToken = null; } // TODO(b/297352617): add timeAppCalledSystemServer to the constructor. SandboxLatencyInfo sandboxLatencyInfo = new SandboxLatencyInfo(SandboxLatencyInfo.METHOD_LOAD_SDK); sandboxLatencyInfo.setTimeAppCalledSystemServer(mTimeProvider.elapsedRealtime()); try { mService.loadSdk( mContext.getPackageName(), appProcessToken, sdkName, sandboxLatencyInfo, params, callbackProxy); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Fetches information about SDKs that are loaded in the sandbox. * * @return List of {@link SandboxedSdk} containing all currently loaded SDKs. */ public @NonNull List getSandboxedSdks() { SandboxLatencyInfo sandboxLatencyInfo = new SandboxLatencyInfo(SandboxLatencyInfo.METHOD_GET_SANDBOXED_SDKS); sandboxLatencyInfo.setTimeAppCalledSystemServer(mTimeProvider.elapsedRealtime()); try { return mService.getSandboxedSdks(mContext.getPackageName(), sandboxLatencyInfo); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Unloads an SDK that has been previously loaded by the caller. * *

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

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. */ public void unloadSdk(@NonNull String sdkName) { Objects.requireNonNull(sdkName, "sdkName should not be null"); try { SandboxLatencyInfo sandboxLatencyInfo = new SandboxLatencyInfo(SandboxLatencyInfo.METHOD_UNLOAD_SDK); sandboxLatencyInfo.setTimeAppCalledSystemServer(mTimeProvider.elapsedRealtime()); mService.unloadSdk(mContext.getPackageName(), sdkName, sandboxLatencyInfo); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Sends a request for a surface package to the SDK. * *

After the 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. * *

When the {@link SurfacePackage} is ready, the {@link OutcomeReceiver#onResult} callback of * the passed {@code receiver} will be invoked. This callback will contain a {@link Bundle} * object, which will contain the key {@link SdkSandboxManager#EXTRA_SURFACE_PACKAGE} whose * associated value is the requested {@link SurfacePackage}. * *

The passed {@code params} must contain the following keys: {@link * SdkSandboxManager#EXTRA_WIDTH_IN_PIXELS}, {@link SdkSandboxManager#EXTRA_HEIGHT_IN_PIXELS}, * {@link SdkSandboxManager#EXTRA_DISPLAY_ID} and {@link SdkSandboxManager#EXTRA_HOST_TOKEN}. If * any of these keys are missing or invalid, an {@link IllegalArgumentException} will be thrown. * *

This API may only be called while the caller is running in the foreground. Calls from the * background will result in returning RequestSurfacePackageException in the {@code receiver}. * * @param sdkName name of the SDK loaded into the SDK sandbox. * @param params the parameters which the client application passes to the SDK. * @param callbackExecutor the {@link Executor} on which to invoke the callback * @param receiver This either returns a {@link Bundle} on success which will contain the key * {@link SdkSandboxManager#EXTRA_SURFACE_PACKAGE} with a {@link SurfacePackage} value, or * {@link RequestSurfacePackageException} on failure. * @throws IllegalArgumentException if {@code params} does not contain all required keys. * @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 * @deprecated This method will no longer be supported through {@link SdkSandboxManager}. Please * consider using androidx.privacysandbox library as an alternative */ @Deprecated public void requestSurfacePackage( @NonNull String sdkName, @NonNull Bundle params, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull OutcomeReceiver 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"); } SandboxLatencyInfo sandboxLatencyInfo = new SandboxLatencyInfo(SandboxLatencyInfo.METHOD_REQUEST_SURFACE_PACKAGE); sandboxLatencyInfo.setTimeAppCalledSystemServer(mTimeProvider.elapsedRealtime()); final RequestSurfacePackageReceiverProxy callbackProxy = new RequestSurfacePackageReceiverProxy(callbackExecutor, receiver, mService); mService.requestSurfacePackage( mContext.getPackageName(), sdkName, hostToken, displayId, width, height, sandboxLatencyInfo, params, callbackProxy); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Starts an {@link Activity} in the SDK sandbox. * *

This function will start a new {@link Activity} in the same task of the passed {@code * fromActivity} and pass it to the SDK that shared the passed {@code sdkActivityToken} that * identifies a request from that SDK to stat this {@link Activity}. * *

The {@link Activity} will not start in the following cases: * *

* * @param fromActivity the {@link Activity} will be used to start the new sandbox {@link * Activity} by calling {@link Activity#startActivity(Intent)} against it. * @param sdkActivityToken the identifier that is shared by the SDK which requests the {@link * Activity}. */ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void startSdkSandboxActivity( @NonNull Activity fromActivity, @NonNull IBinder sdkActivityToken) { if (!SdkLevel.isAtLeastU()) { throw new UnsupportedOperationException(); } long timeEventStarted = mTimeProvider.elapsedRealtime(); Intent intent = new Intent(); intent.setAction(ACTION_START_SANDBOXED_ACTIVITY); intent.setPackage(mContext.getPackageManager().getSdkSandboxPackageName()); Bundle params = new Bundle(); params.putBinder(EXTRA_SANDBOXED_ACTIVITY_HANDLER, sdkActivityToken); params.putLong(EXTRA_SANDBOXED_ACTIVITY_INITIATION_TIME, timeEventStarted); intent.putExtras(params); fromActivity.startActivity(intent); logStartSdkSandboxActivityLatency(timeEventStarted); } // TODO(b/304459399): move Sandbox Activity latency logging to its own class @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) private void logStartSdkSandboxActivityLatency(long timeEventStarted) { try { // TODO(b/305240130): retrieve SDK info from sandbox process mService.logSandboxActivityApiLatency( StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__START_SDK_SANDBOX_ACTIVITY, StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__SUCCESS, (int) (mTimeProvider.elapsedRealtime() - timeEventStarted)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * A callback for tracking events SDK sandbox death. * *

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 */ public static @SandboxLatencyInfo.ResultCode int getResultCodeForLoadSdkException( LoadSdkException exception) { return switch (exception.getLoadSdkErrorCode()) { case LOAD_SDK_NOT_FOUND -> RESULT_CODE_LOAD_SDK_NOT_FOUND; case LOAD_SDK_ALREADY_LOADED -> RESULT_CODE_LOAD_SDK_ALREADY_LOADED; case LOAD_SDK_SDK_DEFINED_ERROR -> RESULT_CODE_LOAD_SDK_SDK_DEFINED_ERROR; case LOAD_SDK_SDK_SANDBOX_DISABLED -> RESULT_CODE_LOAD_SDK_SDK_SANDBOX_DISABLED; case LOAD_SDK_INTERNAL_ERROR -> RESULT_CODE_LOAD_SDK_INTERNAL_ERROR; case SDK_SANDBOX_PROCESS_NOT_AVAILABLE -> RESULT_CODE_SDK_SANDBOX_PROCESS_NOT_AVAILABLE; default -> { Log.e( TAG, "Unexpected load SDK exception code: " + exception.getLoadSdkErrorCode()); yield RESULT_CODE_UNSPECIFIED; } }; } /** @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()); } } /** * Adds keys to set of keys being synced from app's default {@link SharedPreferences} to the SDK * sandbox. * *

Synced data will be available for SDKs to read using the {@link * SdkSandboxController#getClientSharedPreferences()} API. * *

To stop syncing any key that has been added using this API, use {@link * #removeSyncedSharedPreferencesKeys(Set)}. * *

The sync breaks if the app restarts and user must call this API again to rebuild the pool * of keys for syncing. * *

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 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)} * *

Removed keys will be erased from the SDK sandbox 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 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 * the SDK sandbox. */ @NonNull public Set getSyncedSharedPreferencesKeys() { return mSyncManager.getSharedPreferencesSyncKeys(); } /** @hide */ private static class LoadSdkReceiverProxy extends ILoadSdkCallback.Stub { private final Executor mExecutor; private final OutcomeReceiver mCallback; private final ISdkSandboxManager mService; LoadSdkReceiverProxy( Executor executor, OutcomeReceiver callback, ISdkSandboxManager service) { mExecutor = executor; mCallback = callback; mService = service; } @Override public void onLoadSdkSuccess( SandboxedSdk sandboxedSdk, SandboxLatencyInfo sandboxLatencyInfo) { logSandboxApiLatency(sandboxLatencyInfo); mExecutor.execute(() -> mCallback.onResult(sandboxedSdk)); } @Override public void onLoadSdkFailure( LoadSdkException exception, SandboxLatencyInfo sandboxLatencyInfo) { sandboxLatencyInfo.setResultCode(getResultCodeForLoadSdkException(exception)); logSandboxApiLatency(sandboxLatencyInfo); mExecutor.execute(() -> mCallback.onError(exception)); } private void logSandboxApiLatency(SandboxLatencyInfo sandboxLatencyInfo) { sandboxLatencyInfo.setTimeAppReceivedCallFromSystemServer( SystemClock.elapsedRealtime()); try { mService.logSandboxApiLatency(sandboxLatencyInfo); } catch (RemoteException e) { Log.w( TAG, "Remote exception while calling logSandboxApiLatency." + "Error: " + e.getMessage()); } } } /** @hide */ private static class RequestSurfacePackageReceiverProxy extends IRequestSurfacePackageCallback.Stub { private final Executor mExecutor; private final OutcomeReceiver mReceiver; private final ISdkSandboxManager mService; RequestSurfacePackageReceiverProxy( Executor executor, OutcomeReceiver receiver, ISdkSandboxManager service) { mExecutor = executor; mReceiver = receiver; mService = service; } @Override public void onSurfacePackageReady( SurfacePackage surfacePackage, int surfacePackageId, Bundle params, SandboxLatencyInfo sandboxLatencyInfo) { logSandboxApiLatency(sandboxLatencyInfo); mExecutor.execute( () -> { params.putParcelable(EXTRA_SURFACE_PACKAGE, surfacePackage); mReceiver.onResult(params); }); } @Override public void onSurfacePackageError( int errorCode, String errorMsg, SandboxLatencyInfo sandboxLatencyInfo) { logSandboxApiLatency(sandboxLatencyInfo); mExecutor.execute( () -> mReceiver.onError( new RequestSurfacePackageException(errorCode, errorMsg))); } private void logSandboxApiLatency(SandboxLatencyInfo sandboxLatencyInfo) { sandboxLatencyInfo.setTimeAppReceivedCallFromSystemServer( SystemClock.elapsedRealtime()); try { mService.logSandboxApiLatency(sandboxLatencyInfo); } catch (RemoteException e) { Log.w( TAG, "Remote exception while calling logSandboxApiLatency." + "Error: " + e.getMessage()); } } } /** * Return the AdServicesManager * * @hide */ public IBinder getAdServicesManager() { try { return mService.getAdServicesManager(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } }