967 lines
41 KiB
Java
967 lines
41 KiB
Java
/*
|
|
* 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();
|
|
}
|
|
}
|
|
}
|