419 lines
18 KiB
Java
419 lines
18 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.sdkprovider;
|
||
|
|
||
|
import static android.app.sdksandbox.SdkSandboxManager.getResultCodeForLoadSdkException;
|
||
|
import static android.app.sdksandbox.sdkprovider.SdkSandboxController.SDK_SANDBOX_CONTROLLER_SERVICE;
|
||
|
|
||
|
import android.annotation.CallbackExecutor;
|
||
|
import android.annotation.FlaggedApi;
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.SystemService;
|
||
|
import android.app.Activity;
|
||
|
import android.app.sdksandbox.AppOwnedSdkSandboxInterface;
|
||
|
import android.app.sdksandbox.ILoadSdkCallback;
|
||
|
import android.app.sdksandbox.LoadSdkException;
|
||
|
import android.app.sdksandbox.SandboxLatencyInfo;
|
||
|
import android.app.sdksandbox.SandboxedSdk;
|
||
|
import android.app.sdksandbox.SandboxedSdkContext;
|
||
|
import android.app.sdksandbox.SandboxedSdkProvider;
|
||
|
import android.app.sdksandbox.SdkSandboxLocalSingleton;
|
||
|
import android.app.sdksandbox.SdkSandboxManager;
|
||
|
import android.app.sdksandbox.StatsdUtil;
|
||
|
import android.content.Context;
|
||
|
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 androidx.annotation.RequiresApi;
|
||
|
|
||
|
import com.android.modules.utils.build.SdkLevel;
|
||
|
import com.android.sdksandbox.flags.Flags;
|
||
|
|
||
|
import java.util.List;
|
||
|
import java.util.Objects;
|
||
|
import java.util.concurrent.Executor;
|
||
|
|
||
|
/**
|
||
|
* Controller that is used by SDK loaded in the sandbox to access information provided by the sdk
|
||
|
* sandbox.
|
||
|
*
|
||
|
* <p>It enables the SDK to communicate with other SDKS in the SDK sandbox and know about the state
|
||
|
* of the sdks that are currently loaded in it.
|
||
|
*
|
||
|
* <p>An instance of {@link SdkSandboxController} can be obtained using {@link
|
||
|
* Context#getSystemService} and {@link SdkSandboxController class}. The {@link Context} can in turn
|
||
|
* be obtained using {@link android.app.sdksandbox.SandboxedSdkProvider#getContext()}.
|
||
|
*/
|
||
|
@SystemService(SDK_SANDBOX_CONTROLLER_SERVICE)
|
||
|
public class SdkSandboxController {
|
||
|
public static final String SDK_SANDBOX_CONTROLLER_SERVICE = "sdk_sandbox_controller_service";
|
||
|
/** @hide */
|
||
|
public static final String CLIENT_SHARED_PREFERENCES_NAME =
|
||
|
"com.android.sdksandbox.client_sharedpreferences";
|
||
|
|
||
|
private static final String TAG = "SdkSandboxController";
|
||
|
|
||
|
private SdkSandboxLocalSingleton mSdkSandboxLocalSingleton;
|
||
|
private SdkSandboxActivityRegistry mSdkSandboxActivityRegistry;
|
||
|
private Context mContext;
|
||
|
|
||
|
/**
|
||
|
* Create SdkSandboxController.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public SdkSandboxController(@NonNull Context context) {
|
||
|
// When SdkSandboxController is initiated from inside the sdk sandbox process, its private
|
||
|
// members will be immediately rewritten by the initialize method.
|
||
|
initialize(context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initializes {@link SdkSandboxController} with the given {@code context}.
|
||
|
*
|
||
|
* <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context.
|
||
|
* For more information check the javadoc on the {@link
|
||
|
* android.app.sdksandbox.SdkSandboxSystemServiceRegistry}.
|
||
|
*
|
||
|
* @hide
|
||
|
* @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry
|
||
|
*/
|
||
|
public SdkSandboxController initialize(@NonNull Context context) {
|
||
|
mContext = context;
|
||
|
mSdkSandboxLocalSingleton = SdkSandboxLocalSingleton.getExistingInstance();
|
||
|
mSdkSandboxActivityRegistry = SdkSandboxActivityRegistry.getInstance();
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fetches all {@link AppOwnedSdkSandboxInterface} that are registered by the app.
|
||
|
*
|
||
|
* @return List of {@link AppOwnedSdkSandboxInterface} containing all currently registered
|
||
|
* AppOwnedSdkSandboxInterface.
|
||
|
* @throws UnsupportedOperationException if the controller is obtained from an unexpected
|
||
|
* context. Use {@link SandboxedSdkProvider#getContext()} for the right context
|
||
|
*/
|
||
|
public @NonNull List<AppOwnedSdkSandboxInterface> getAppOwnedSdkSandboxInterfaces() {
|
||
|
enforceSandboxedSdkContextInitialization();
|
||
|
try {
|
||
|
return mSdkSandboxLocalSingleton
|
||
|
.getSdkToServiceCallback()
|
||
|
.getAppOwnedSdkSandboxInterfaces(
|
||
|
((SandboxedSdkContext) mContext).getClientPackageName());
|
||
|
} 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
|
||
|
* @throws UnsupportedOperationException if the controller is obtained from an unexpected
|
||
|
* context. Use {@link SandboxedSdkProvider#getContext()} for the right context
|
||
|
*/
|
||
|
public @NonNull List<SandboxedSdk> getSandboxedSdks() {
|
||
|
enforceSandboxedSdkContextInitialization();
|
||
|
SandboxLatencyInfo sandboxLatencyInfo =
|
||
|
new SandboxLatencyInfo(SandboxLatencyInfo.METHOD_GET_SANDBOXED_SDKS_VIA_CONTROLLER);
|
||
|
// TODO(b/319659746) : Rename the method to something more generic than using App.
|
||
|
// TODO(b/321909787) : Use injector to set time in order to write unit tests.
|
||
|
sandboxLatencyInfo.setTimeAppCalledSystemServer(SystemClock.elapsedRealtime());
|
||
|
|
||
|
try {
|
||
|
return mSdkSandboxLocalSingleton
|
||
|
.getSdkToServiceCallback()
|
||
|
.getSandboxedSdks(
|
||
|
((SandboxedSdkContext) mContext).getClientPackageName(),
|
||
|
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 may only load {@code SDKs} the client app depends on into the SDK sandbox.
|
||
|
*
|
||
|
* @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}.
|
||
|
* @throws UnsupportedOperationException if the controller is obtained from an unexpected
|
||
|
* context. Use {@link SandboxedSdkProvider#getContext()} for the right context
|
||
|
*/
|
||
|
public void loadSdk(
|
||
|
@NonNull String sdkName,
|
||
|
@NonNull Bundle params,
|
||
|
@NonNull @CallbackExecutor Executor executor,
|
||
|
@NonNull OutcomeReceiver<SandboxedSdk, LoadSdkException> receiver) {
|
||
|
enforceSandboxedSdkContextInitialization();
|
||
|
final LoadSdkReceiverProxy callbackProxy = new LoadSdkReceiverProxy(executor, receiver);
|
||
|
SandboxLatencyInfo sandboxLatencyInfo =
|
||
|
new SandboxLatencyInfo(SandboxLatencyInfo.METHOD_LOAD_SDK_VIA_CONTROLLER);
|
||
|
// TODO(b/319659746) : Rename the method to something more generic than using App.
|
||
|
// TODO(b/321909787) : Use injector to set time in order to write unit tests.
|
||
|
sandboxLatencyInfo.setTimeAppCalledSystemServer(SystemClock.elapsedRealtime());
|
||
|
|
||
|
try {
|
||
|
mSdkSandboxLocalSingleton
|
||
|
.getSdkToServiceCallback()
|
||
|
.loadSdk(
|
||
|
((SandboxedSdkContext) mContext).getClientPackageName(),
|
||
|
sdkName,
|
||
|
sandboxLatencyInfo,
|
||
|
params,
|
||
|
callbackProxy);
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns {@link SharedPreferences} containing data synced from the client app.
|
||
|
*
|
||
|
* <p>Keys that have been synced by the client app using {@link
|
||
|
* SdkSandboxManager#addSyncedSharedPreferencesKeys(Set)} can be found in this {@link
|
||
|
* SharedPreferences}.
|
||
|
*
|
||
|
* <p>The returned {@link SharedPreferences} should only be read. Writing to it is not
|
||
|
* supported.
|
||
|
*
|
||
|
* @return {@link SharedPreferences} containing data synced from client app.
|
||
|
* @throws UnsupportedOperationException if the controller is obtained from an unexpected
|
||
|
* context. Use {@link SandboxedSdkProvider#getContext()} for the right context
|
||
|
*/
|
||
|
@NonNull
|
||
|
public SharedPreferences getClientSharedPreferences() {
|
||
|
enforceSandboxedSdkContextInitialization();
|
||
|
|
||
|
// TODO(b/248214708): We should store synced data in a separate internal storage directory.
|
||
|
return mContext.getApplicationContext()
|
||
|
.getSharedPreferences(CLIENT_SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an identifier for a {@link SdkSandboxActivityHandler} after registering it.
|
||
|
*
|
||
|
* <p>This function registers an implementation of {@link SdkSandboxActivityHandler} created by
|
||
|
* an SDK and returns an {@link IBinder} which uniquely identifies the passed {@link
|
||
|
* SdkSandboxActivityHandler} object.
|
||
|
*
|
||
|
* <p>If the same {@link SdkSandboxActivityHandler} registered multiple times without
|
||
|
* unregistering, the same {@link IBinder} token will be returned.
|
||
|
*
|
||
|
* @param sdkSandboxActivityHandler is the {@link SdkSandboxActivityHandler} to register.
|
||
|
* @return {@link IBinder} uniquely identify the passed {@link SdkSandboxActivityHandler}.
|
||
|
*/
|
||
|
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||
|
@NonNull
|
||
|
public IBinder registerSdkSandboxActivityHandler(
|
||
|
@NonNull SdkSandboxActivityHandler sdkSandboxActivityHandler) {
|
||
|
if (!SdkLevel.isAtLeastU()) {
|
||
|
throw new UnsupportedOperationException();
|
||
|
}
|
||
|
// TODO(b/321909787) : Use injector to set time for unit tests.
|
||
|
long timeEventStarted = SystemClock.elapsedRealtime();
|
||
|
enforceSandboxedSdkContextInitialization();
|
||
|
|
||
|
IBinder token =
|
||
|
mSdkSandboxActivityRegistry.register(
|
||
|
(SandboxedSdkContext) mContext, sdkSandboxActivityHandler);
|
||
|
logSandboxActivityApiLatency(
|
||
|
StatsdUtil
|
||
|
.SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__REGISTER_SDK_SANDBOX_ACTIVITY_HANDLER,
|
||
|
StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__SUCCESS,
|
||
|
timeEventStarted);
|
||
|
return token;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Unregister an already registered {@link SdkSandboxActivityHandler}.
|
||
|
*
|
||
|
* <p>If the passed {@link SdkSandboxActivityHandler} is registered, it will be unregistered.
|
||
|
* Otherwise, it will do nothing.
|
||
|
*
|
||
|
* <p>After unregistering, SDK can register the same handler object again or create a new one in
|
||
|
* case it wants a new {@link Activity}.
|
||
|
*
|
||
|
* <p>If the {@link IBinder} token of the unregistered handler used to start a {@link Activity},
|
||
|
* the {@link Activity} will fail to start.
|
||
|
*
|
||
|
* @param sdkSandboxActivityHandler is the {@link SdkSandboxActivityHandler} to unregister.
|
||
|
*/
|
||
|
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||
|
@NonNull
|
||
|
public void unregisterSdkSandboxActivityHandler(
|
||
|
@NonNull SdkSandboxActivityHandler sdkSandboxActivityHandler) {
|
||
|
if (!SdkLevel.isAtLeastU()) {
|
||
|
throw new UnsupportedOperationException();
|
||
|
}
|
||
|
long timeEventStarted = SystemClock.elapsedRealtime();
|
||
|
enforceSandboxedSdkContextInitialization();
|
||
|
|
||
|
mSdkSandboxActivityRegistry.unregister(sdkSandboxActivityHandler);
|
||
|
logSandboxActivityApiLatency(
|
||
|
StatsdUtil
|
||
|
.SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__UNREGISTER_SDK_SANDBOX_ACTIVITY_HANDLER,
|
||
|
StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__SUCCESS,
|
||
|
timeEventStarted);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the package name of the client app.
|
||
|
*
|
||
|
* @throws UnsupportedOperationException if the controller is obtained from an unexpected
|
||
|
* context. Use {@link SandboxedSdkProvider#getContext()} for the right context.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public String getClientPackageName() {
|
||
|
enforceSandboxedSdkContextInitialization();
|
||
|
return ((SandboxedSdkContext) mContext).getClientPackageName();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Registers a listener to be notified of changes in the client's {@link
|
||
|
* android.app.ActivityManager.RunningAppProcessInfo#importance}.
|
||
|
*
|
||
|
* @param listener an implementation of {@link SdkSandboxClientImportanceListener} to register.
|
||
|
* @throws UnsupportedOperationException if the controller is obtained from an unexpected
|
||
|
* context. Use {@link SandboxedSdkProvider#getContext()} for the right context.
|
||
|
*/
|
||
|
@FlaggedApi(Flags.FLAG_SANDBOX_CLIENT_IMPORTANCE_LISTENER)
|
||
|
public void registerSdkSandboxClientImportanceListener(
|
||
|
@NonNull @CallbackExecutor Executor executor,
|
||
|
@NonNull SdkSandboxClientImportanceListener listener) {
|
||
|
Objects.requireNonNull(executor, "executor should not be null");
|
||
|
Objects.requireNonNull(listener, "listener should not be null");
|
||
|
enforceSandboxedSdkContextInitialization();
|
||
|
SdkSandboxLocalSingleton.getExistingInstance()
|
||
|
.registerSdkSandboxClientImportanceListener(
|
||
|
new SdkSandboxClientImportanceListenerProxy(executor, listener));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Unregisters a listener previously registered using {@link
|
||
|
* SdkSandboxController#registerSdkSandboxClientImportanceListener(Executor,
|
||
|
* SdkSandboxClientImportanceListener)}
|
||
|
*
|
||
|
* @param listener an implementation of {@link SdkSandboxClientImportanceListener} to
|
||
|
* unregister.
|
||
|
* @throws UnsupportedOperationException if the controller is obtained from an unexpected
|
||
|
* context. Use {@link SandboxedSdkProvider#getContext()} for the right context.
|
||
|
*/
|
||
|
@FlaggedApi(Flags.FLAG_SANDBOX_CLIENT_IMPORTANCE_LISTENER)
|
||
|
public void unregisterSdkSandboxClientImportanceListener(
|
||
|
@NonNull SdkSandboxClientImportanceListener listener) {
|
||
|
Objects.requireNonNull(listener, "listener should not be null");
|
||
|
enforceSandboxedSdkContextInitialization();
|
||
|
SdkSandboxLocalSingleton.getExistingInstance()
|
||
|
.unregisterSdkSandboxClientImportanceListener(listener);
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
public static class SdkSandboxClientImportanceListenerProxy {
|
||
|
private final Executor mExecutor;
|
||
|
public final SdkSandboxClientImportanceListener listener;
|
||
|
|
||
|
SdkSandboxClientImportanceListenerProxy(
|
||
|
Executor executor, SdkSandboxClientImportanceListener listener) {
|
||
|
mExecutor = executor;
|
||
|
this.listener = listener;
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
public void onForegroundImportanceChanged(boolean isForeground) {
|
||
|
mExecutor.execute(() -> listener.onForegroundImportanceChanged(isForeground));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void enforceSandboxedSdkContextInitialization() {
|
||
|
if (!(mContext instanceof SandboxedSdkContext)) {
|
||
|
throw new UnsupportedOperationException(
|
||
|
"Only available from the context obtained by calling android.app.sdksandbox"
|
||
|
+ ".SandboxedSdkProvider#getContext()");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void logLatenciesFromSandbox(SandboxLatencyInfo sandboxLatencyInfo) {
|
||
|
// TODO(b/319659746) : Rename the method to something more generic than using App.
|
||
|
sandboxLatencyInfo.setTimeAppReceivedCallFromSystemServer(SystemClock.elapsedRealtime());
|
||
|
try {
|
||
|
mSdkSandboxLocalSingleton
|
||
|
.getSdkToServiceCallback()
|
||
|
.logLatenciesFromSandbox(sandboxLatencyInfo);
|
||
|
} catch (RemoteException e) {
|
||
|
Log.e(
|
||
|
TAG,
|
||
|
"Logging metrics for method "
|
||
|
+ sandboxLatencyInfo.getMethod()
|
||
|
+ " failed with exception "
|
||
|
+ e.getMessage());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void logSandboxActivityApiLatency(int method, int callResult, long timeEventStarted) {
|
||
|
try {
|
||
|
mSdkSandboxLocalSingleton
|
||
|
.getSdkToServiceCallback()
|
||
|
.logSandboxActivityApiLatencyFromSandbox(
|
||
|
method,
|
||
|
callResult,
|
||
|
(int) (SystemClock.elapsedRealtime() - timeEventStarted));
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class LoadSdkReceiverProxy extends ILoadSdkCallback.Stub {
|
||
|
private final Executor mExecutor;
|
||
|
private final OutcomeReceiver<SandboxedSdk, LoadSdkException> mCallback;
|
||
|
|
||
|
LoadSdkReceiverProxy(
|
||
|
Executor executor, OutcomeReceiver<SandboxedSdk, LoadSdkException> callback) {
|
||
|
mExecutor = executor;
|
||
|
mCallback = callback;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onLoadSdkSuccess(
|
||
|
SandboxedSdk sandboxedSdk, SandboxLatencyInfo sandboxLatencyInfo) {
|
||
|
SdkSandboxController.this.logLatenciesFromSandbox(sandboxLatencyInfo);
|
||
|
mExecutor.execute(() -> mCallback.onResult(sandboxedSdk));
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onLoadSdkFailure(
|
||
|
LoadSdkException exception, SandboxLatencyInfo sandboxLatencyInfo) {
|
||
|
sandboxLatencyInfo.setResultCode(getResultCodeForLoadSdkException(exception));
|
||
|
SdkSandboxController.this.logLatenciesFromSandbox(sandboxLatencyInfo);
|
||
|
mExecutor.execute(() -> mCallback.onError(exception));
|
||
|
}
|
||
|
}
|
||
|
}
|