| /* |
| * 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. |
| * |
| * <p>SDK sandbox is a java process running in a separate uid range. Each app may have its own SDK |
| * sandbox process. |
| * |
| * <p>The app first needs to declare SDKs it depends on in its manifest using the {@code |
| * <uses-sdk-library>} tag. Apps may only load SDKs they depend on into the SDK sandbox. |
| * |
| * @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 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. |
| * |
| * <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; |
| |
| /** |
| * Action name for the intent which starts {@link Activity} in SDK sandbox. |
| * |
| * <p>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. |
| * |
| * <p>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}. |
| * |
| * <p>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}. |
| * |
| * <p>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. |
| * |
| * <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 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<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); |
| 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. |
| * |
| * <p>Registering an {@link AppOwnedSdkSandboxInterface} that has same name as a previously |
| * registered interface will result in {@link IllegalStateException}. |
| * |
| * <p>{@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<AppOwnedSdkSandboxInterface> 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. |
| * |
| * <p>Loads SDK library with {@code sdkName} to an SDK sandbox process asynchronously. The |
| * caller will be notified through the {@code receiver}. |
| * |
| * <p>The caller should already declare {@code SDKs} it depends on in its manifest using {@code |
| * <uses-sdk-library>} tag. The caller may only load {@code SDKs} it depends on into the SDK |
| * sandbox. |
| * |
| * <p>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. |
| * |
| * <p>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<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); |
| |
| 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<SandboxedSdk> 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. |
| * |
| * <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. |
| */ |
| 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. |
| * |
| * <p>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. |
| * |
| * <p>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}. |
| * |
| * <p>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. |
| * |
| * <p>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<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"); |
| } |
| |
| 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. |
| * |
| * <p>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}. |
| * |
| * <p>The {@link Activity} will not start in the following cases: |
| * |
| * <ul> |
| * <li>The App calling this API is in the background. |
| * <li>The passed {@code sdkActivityToken} does not map to a request for an {@link Activity} |
| * form the SDK that shared it with the caller app. |
| * <li>The SDK that shared the passed {@code sdkActivityToken} removed its request for this |
| * {@link Activity}. |
| * <li>The sandbox {@link Activity} is already created. |
| * </ul> |
| * |
| * @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. |
| * |
| * <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 */ |
| 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. |
| * |
| * <p>Synced data will be available for SDKs to read using the {@link |
| * SdkSandboxController#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 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<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 |
| * the SDK sandbox. |
| */ |
| @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, 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<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, |
| 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(); |
| } |
| } |
| } |