/* * 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
* 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 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 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
* 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 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 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:
*
* 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 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
*
*
* @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.
*
*