blob: edf69b231fd568f9d135c3803e536ab6460b0f00 [file] [log] [blame]
/*
* 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 com.android.tests.sdksandbox.endtoend;
import static android.app.sdksandbox.SdkSandboxManager.EXTRA_DISPLAY_ID;
import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HEIGHT_IN_PIXELS;
import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HOST_TOKEN;
import static android.app.sdksandbox.SdkSandboxManager.EXTRA_WIDTH_IN_PIXELS;
import static android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_INTERNAL_ERROR;
import static androidx.lifecycle.Lifecycle.State;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.sdksandbox.AppOwnedSdkSandboxInterface;
import android.app.sdksandbox.LoadSdkException;
import android.app.sdksandbox.SandboxedSdk;
import android.app.sdksandbox.SdkSandboxManager;
import android.app.sdksandbox.testutils.FakeLoadSdkCallback;
import android.app.sdksandbox.testutils.FakeRequestSurfacePackageCallback;
import android.app.sdksandbox.testutils.FakeSdkSandboxProcessDeathCallback;
import android.app.sdksandbox.testutils.SdkSandboxDeviceSupportedRule;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.view.Surface;
import androidx.lifecycle.Lifecycle;
import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
import com.android.compatibility.common.util.DeviceConfigStateHelper;
import com.android.ctssdkprovider.IActivityActionExecutor;
import com.android.ctssdkprovider.IActivityStarter;
import com.android.ctssdkprovider.ICtsSdkProviderApi;
import com.android.modules.utils.build.SdkLevel;
import com.google.common.truth.Expect;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
/** End-to-end tests of {@link SdkSandboxManager} APIs. */
@RunWith(JUnit4.class)
public final class SdkSandboxManagerTest {
private static final String TAG = SdkSandboxManagerTest.class.getSimpleName();
private static final String NON_EXISTENT_SDK = "com.android.not_exist";
private static final String APP_OWNED_SDK_SANDBOX_INTERFACE_NAME =
"com.android.ctsappownedsdksandboxinterface";
private static final String SDK_NAME_1 = "com.android.ctssdkprovider";
private static final String SDK_NAME_2 = "com.android.emptysdkprovider";
private static final String TEST_OPTION = "test-option";
private static final String OPTION_THROW_INTERNAL_ERROR = "internal-error";
private static final String OPTION_THROW_REQUEST_SURFACE_PACKAGE_ERROR = "rsp-error";
private static final String NAMESPACE_WINDOW_MANAGER = "window_manager";
private static final String ASM_RESTRICTIONS_ENABLED =
"ActivitySecurity__asm_restrictions_enabled";
private static final String UNREGISTER_BEFORE_STARTING_KEY = "UNREGISTER_BEFORE_STARTING_KEY";
private static final String ACTIVITY_STARTER_KEY = "ACTIVITY_STARTER_KEY";
private static final String TEXT_KEY = "TEXT_KEY";
private static final int WAIT_FOR_TEXT_IN_MS = 1000;
private static final UiDevice sUiDevice = UiDevice.getInstance(getInstrumentation());
@Rule(order = 0)
public final SdkSandboxDeviceSupportedRule supportedRule = new SdkSandboxDeviceSupportedRule();
@Rule(order = 1)
public final ActivityScenarioRule<TestActivity> activityScenarioRule =
new ActivityScenarioRule<>(TestActivity.class);
@Rule(order = 2)
public final Expect expect = Expect.create();
private ActivityScenario<TestActivity> mScenario;
private SdkSandboxManager mSdkSandboxManager;
private final DeviceConfigStateHelper mDeviceConfig =
new DeviceConfigStateHelper(NAMESPACE_WINDOW_MANAGER);
private final Random mRandom = new Random();
@Before
public void setup() throws Exception {
Context context = InstrumentationRegistry.getInstrumentation().getContext();
mSdkSandboxManager = context.getSystemService(SdkSandboxManager.class);
killSandboxIfExists();
mScenario = activityScenarioRule.getScenario();
mDeviceConfig.set(ASM_RESTRICTIONS_ENABLED, "1");
sUiDevice.setOrientationNatural();
}
@Test
public void testGetSdkSandboxState() {
int state = SdkSandboxManager.getSdkSandboxState();
assertThat(state).isEqualTo(SdkSandboxManager.SDK_SANDBOX_STATE_ENABLED_PROCESS_ISOLATION);
}
@Test
public void testLoadSdkSuccessfully() {
final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_1, new Bundle(), Runnable::run, callback);
callback.assertLoadSdkIsSuccessful();
assertNotNull(callback.getSandboxedSdk());
assertNotNull(callback.getSandboxedSdk().getInterface());
}
@Test
public void testRegisterAndGetAppOwnedSdkSandboxInterface() throws Exception {
try {
IBinder iBinder = new Binder();
mSdkSandboxManager.registerAppOwnedSdkSandboxInterface(
new AppOwnedSdkSandboxInterface(
APP_OWNED_SDK_SANDBOX_INTERFACE_NAME,
/*version=*/ 0,
/*interfaceIBinder=*/ iBinder));
final List<AppOwnedSdkSandboxInterface> appOwnedSdkSandboxInterfaceList =
mSdkSandboxManager.getAppOwnedSdkSandboxInterfaces();
assertThat(appOwnedSdkSandboxInterfaceList).hasSize(1);
assertThat(appOwnedSdkSandboxInterfaceList.get(0).getName())
.isEqualTo(APP_OWNED_SDK_SANDBOX_INTERFACE_NAME);
assertThat(appOwnedSdkSandboxInterfaceList.get(0).getVersion()).isEqualTo(0);
assertThat(appOwnedSdkSandboxInterfaceList.get(0).getInterface()).isEqualTo(iBinder);
} finally {
mSdkSandboxManager.unregisterAppOwnedSdkSandboxInterface(
APP_OWNED_SDK_SANDBOX_INTERFACE_NAME);
}
}
@Test
public void testUnregisterAppOwnedSdkSandboxInterface() throws Exception {
mSdkSandboxManager.registerAppOwnedSdkSandboxInterface(
new AppOwnedSdkSandboxInterface(
APP_OWNED_SDK_SANDBOX_INTERFACE_NAME,
/*version=*/ 0,
/*interfaceIBinder=*/ new Binder()));
mSdkSandboxManager.unregisterAppOwnedSdkSandboxInterface(
APP_OWNED_SDK_SANDBOX_INTERFACE_NAME);
assertThat(mSdkSandboxManager.getAppOwnedSdkSandboxInterfaces()).hasSize(0);
}
@Test
public void testRegisterAppOwnedSdkSandboxInterfaceAlreadyRegistered() throws Exception {
try {
mSdkSandboxManager.registerAppOwnedSdkSandboxInterface(
new AppOwnedSdkSandboxInterface(
APP_OWNED_SDK_SANDBOX_INTERFACE_NAME,
/*version=*/ 0,
/*interfaceIBinder=*/ new Binder()));
assertThrows(
RuntimeException.class,
() ->
mSdkSandboxManager.registerAppOwnedSdkSandboxInterface(
new AppOwnedSdkSandboxInterface(
APP_OWNED_SDK_SANDBOX_INTERFACE_NAME,
/*version=*/ 0,
/*interfaceIBinder=*/ new Binder())));
} finally {
mSdkSandboxManager.unregisterAppOwnedSdkSandboxInterface(
APP_OWNED_SDK_SANDBOX_INTERFACE_NAME);
}
}
@Test
public void testGetSandboxedSdkSuccessfully() {
loadSdk();
List<SandboxedSdk> sandboxedSdks = mSdkSandboxManager.getSandboxedSdks();
assertThat(sandboxedSdks.size()).isEqualTo(1);
assertThat(sandboxedSdks.get(0).getSharedLibraryInfo().getName()).isEqualTo(SDK_NAME_1);
mSdkSandboxManager.unloadSdk(SDK_NAME_1);
List<SandboxedSdk> sandboxedSdksAfterUnload = mSdkSandboxManager.getSandboxedSdks();
assertThat(sandboxedSdksAfterUnload.size()).isEqualTo(0);
}
@Test
public void testLoadSdkAndCheckClassloader() throws Exception {
ICtsSdkProviderApi sdk = loadSdk();
sdk.checkClassloaders();
}
@Test
public void testGetOpPackageName() throws Exception {
ICtsSdkProviderApi sdk = loadSdk();
final PackageManager pm =
InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
assertThat(sdk.getOpPackageName()).isEqualTo(pm.getSdkSandboxPackageName());
}
@Test
public void testRetryLoadSameSdkShouldFail() {
FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_1, new Bundle(), Runnable::run, callback);
callback.assertLoadSdkIsSuccessful();
callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_1, new Bundle(), Runnable::run, callback);
callback.assertLoadSdkIsUnsuccessful();
assertThat(callback.getLoadSdkErrorCode())
.isEqualTo(SdkSandboxManager.LOAD_SDK_ALREADY_LOADED);
}
@Test
public void testLoadNonExistentSdk() {
final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(NON_EXISTENT_SDK, new Bundle(), Runnable::run, callback);
callback.assertLoadSdkIsUnsuccessful();
assertThat(callback.getLoadSdkErrorCode())
.isEqualTo(SdkSandboxManager.LOAD_SDK_NOT_FOUND);
LoadSdkException loadSdkException = callback.getLoadSdkException();
assertThat(loadSdkException.getExtraInformation()).isNotNull();
assertThat(loadSdkException.getExtraInformation().isEmpty()).isTrue();
}
@Test
public void testLoadSdkWithInternalErrorShouldFail() throws Exception {
final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
Bundle params = new Bundle();
params.putString(TEST_OPTION, OPTION_THROW_INTERNAL_ERROR);
mSdkSandboxManager.loadSdk(SDK_NAME_1, params, Runnable::run, callback);
callback.assertLoadSdkIsUnsuccessful();
assertThat(callback.getLoadSdkErrorCode())
.isEqualTo(SdkSandboxManager.LOAD_SDK_SDK_DEFINED_ERROR);
}
@Test
public void testUnloadAndReloadSdk() throws Exception {
final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_1, new Bundle(), Runnable::run, callback);
callback.assertLoadSdkIsSuccessful();
mSdkSandboxManager.unloadSdk(SDK_NAME_1);
// Wait till SDK is unloaded.
Thread.sleep(2000);
// Calls to an unloaded SDK should fail.
final FakeRequestSurfacePackageCallback requestSurfacePackageCallback =
new FakeRequestSurfacePackageCallback();
mSdkSandboxManager.requestSurfacePackage(
SDK_NAME_1,
getRequestSurfacePackageParams(),
Runnable::run,
requestSurfacePackageCallback);
assertThat(requestSurfacePackageCallback.isRequestSurfacePackageSuccessful()).isFalse();
assertThat(requestSurfacePackageCallback.getSurfacePackageErrorCode())
.isEqualTo(SdkSandboxManager.REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED);
// SDK can be reloaded after being unloaded.
final FakeLoadSdkCallback callback2 = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_1, new Bundle(), Runnable::run, callback2);
callback2.assertLoadSdkIsSuccessful();
}
@Test
public void testUnloadNonexistentSdk() {
final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_1, new Bundle(), Runnable::run, callback);
callback.assertLoadSdkIsSuccessful();
final String nonexistentSdk = "com.android.nonexistent";
// Unloading does nothing - call should go through without error.
mSdkSandboxManager.unloadSdk(nonexistentSdk);
}
@Test
public void testReloadingSdkDoesNotInvalidateIt() {
final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_1, new Bundle(), Runnable::run, callback);
callback.assertLoadSdkIsSuccessful();
SandboxedSdk sandboxedSdk = callback.getSandboxedSdk();
assertNotNull(sandboxedSdk.getInterface());
// Attempt to load the SDK again and see that it fails.
final FakeLoadSdkCallback reloadCallback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_1, new Bundle(), Runnable::run, reloadCallback);
reloadCallback.assertLoadSdkIsUnsuccessful();
// SDK's interface should still be obtainable.
assertNotNull(sandboxedSdk.getInterface());
// Further calls to the SDK should still be valid.
final FakeRequestSurfacePackageCallback surfacePackageCallback =
new FakeRequestSurfacePackageCallback();
mSdkSandboxManager.requestSurfacePackage(
SDK_NAME_1,
getRequestSurfacePackageParams(),
Runnable::run,
surfacePackageCallback);
assertThat(surfacePackageCallback.isRequestSurfacePackageSuccessful()).isTrue();
}
@Test
public void testReloadingSdkAfterKillingSandboxIsSuccessful() throws Exception {
// Kill the sandbox if it already exists from previous tests
killSandboxIfExists();
FakeSdkSandboxProcessDeathCallback callback = new FakeSdkSandboxProcessDeathCallback();
mSdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run, callback);
assertThat(callback.waitForSandboxDeath()).isFalse();
// Killing the sandbox and loading the same SDKs again multiple times should work
for (int i = 1; i <= 3; ++i) {
// The same SDKs should be able to be loaded again after sandbox death
loadMultipleSdks();
callback.resetLatch();
killSandbox();
assertThat(callback.waitForSandboxDeath()).isTrue();
}
}
@Test
public void testAddSdkSandboxProcessDeathCallback_BeforeStartingSandbox() throws Exception {
// Kill the sandbox if it already exists from previous tests
killSandboxIfExists();
// Add a sandbox lifecycle callback before starting the sandbox
FakeSdkSandboxProcessDeathCallback lifecycleCallback =
new FakeSdkSandboxProcessDeathCallback();
mSdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run, lifecycleCallback);
// Bring up the sandbox
loadSdk();
killSandbox();
assertThat(lifecycleCallback.waitForSandboxDeath()).isTrue();
}
@Test
public void testAddSdkSandboxProcessDeathCallback_AfterStartingSandbox() throws Exception {
// Bring up the sandbox
loadSdk();
// Add a sandbox lifecycle callback before starting the sandbox
FakeSdkSandboxProcessDeathCallback lifecycleCallback =
new FakeSdkSandboxProcessDeathCallback();
mSdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run, lifecycleCallback);
killSandbox();
assertThat(lifecycleCallback.waitForSandboxDeath()).isTrue();
}
@Test
public void testRegisterMultipleSdkSandboxProcessDeathCallbacks() throws Exception {
// Kill the sandbox if it already exists from previous tests
killSandboxIfExists();
// Add a sandbox lifecycle callback before starting the sandbox
FakeSdkSandboxProcessDeathCallback lifecycleCallback1 =
new FakeSdkSandboxProcessDeathCallback();
mSdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run, lifecycleCallback1);
// Bring up the sandbox
loadSdk();
// Add another sandbox lifecycle callback after starting it
FakeSdkSandboxProcessDeathCallback lifecycleCallback2 =
new FakeSdkSandboxProcessDeathCallback();
mSdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run, lifecycleCallback2);
killSandbox();
assertThat(lifecycleCallback1.waitForSandboxDeath()).isTrue();
assertThat(lifecycleCallback2.waitForSandboxDeath()).isTrue();
}
@Test
public void testRemoveSdkSandboxProcessDeathCallback() throws Exception {
// Bring up the sandbox
loadSdk();
// Add and remove a sandbox lifecycle callback
FakeSdkSandboxProcessDeathCallback lifecycleCallback1 =
new FakeSdkSandboxProcessDeathCallback();
mSdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run, lifecycleCallback1);
mSdkSandboxManager.removeSdkSandboxProcessDeathCallback(lifecycleCallback1);
// Add a lifecycle callback but don't remove it
FakeSdkSandboxProcessDeathCallback lifecycleCallback2 =
new FakeSdkSandboxProcessDeathCallback();
mSdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run, lifecycleCallback2);
killSandbox();
assertThat(lifecycleCallback1.waitForSandboxDeath()).isFalse();
assertThat(lifecycleCallback2.waitForSandboxDeath()).isTrue();
}
@Test
public void testRequestSurfacePackageSuccessfully() {
loadSdk();
final FakeRequestSurfacePackageCallback surfacePackageCallback =
new FakeRequestSurfacePackageCallback();
mSdkSandboxManager.requestSurfacePackage(
SDK_NAME_1,
getRequestSurfacePackageParams(),
Runnable::run,
surfacePackageCallback);
assertThat(surfacePackageCallback.isRequestSurfacePackageSuccessful()).isTrue();
}
@Test
public void testRequestSurfacePackageWithInternalErrorShouldFail() {
loadSdk();
final FakeRequestSurfacePackageCallback surfacePackageCallback =
new FakeRequestSurfacePackageCallback();
Bundle params = getRequestSurfacePackageParams();
params.putString(TEST_OPTION, OPTION_THROW_REQUEST_SURFACE_PACKAGE_ERROR);
mSdkSandboxManager.requestSurfacePackage(
SDK_NAME_1, params, Runnable::run, surfacePackageCallback);
assertThat(surfacePackageCallback.isRequestSurfacePackageSuccessful()).isFalse();
assertThat(surfacePackageCallback.getSurfacePackageErrorCode())
.isEqualTo(SdkSandboxManager.REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR);
assertThat(surfacePackageCallback.getExtraErrorInformation()).isNotNull();
assertThat(surfacePackageCallback.getExtraErrorInformation().isEmpty()).isTrue();
}
@Test
public void testRequestSurfacePackage_SandboxDiesAfterLoadingSdk() throws Exception {
loadSdk();
assertThat(killSandboxIfExists()).isTrue();
final FakeRequestSurfacePackageCallback surfacePackageCallback =
new FakeRequestSurfacePackageCallback();
mSdkSandboxManager.requestSurfacePackage(
SDK_NAME_1,
getRequestSurfacePackageParams(),
Runnable::run,
surfacePackageCallback);
assertThat(surfacePackageCallback.isRequestSurfacePackageSuccessful()).isFalse();
assertThat(surfacePackageCallback.getSurfacePackageErrorCode())
.isEqualTo(SdkSandboxManager.REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED);
}
@Test
public void testResourcesAndAssets() throws Exception {
ICtsSdkProviderApi sdk = loadSdk();
sdk.checkResourcesAndAssets();
}
@Test
public void testLoadSdkInBackgroundFails() throws Exception {
mScenario.moveToState(Lifecycle.State.DESTROYED);
// Wait for the activity to be destroyed
Thread.sleep(1000);
final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_1, new Bundle(), Runnable::run, callback);
LoadSdkException thrown = callback.getLoadSdkException();
assertEquals(LOAD_SDK_INTERNAL_ERROR, thrown.getLoadSdkErrorCode());
assertThat(thrown).hasMessageThat().contains("does not run in the foreground");
}
@Test
public void testSandboxApisAreUsableAfterUnbindingSandbox() throws Exception {
FakeLoadSdkCallback callback1 = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_1, new Bundle(), Runnable::run, callback1);
callback1.assertLoadSdkIsSuccessful();
// Move the app to the background and bring it back to the foreground again.
mScenario.recreate();
// Loading another sdk should work without issue
FakeLoadSdkCallback callback2 = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_2, new Bundle(), Runnable::run, callback2);
callback2.assertLoadSdkIsSuccessful();
// Requesting surface package from the first loaded sdk should work.
final FakeRequestSurfacePackageCallback surfacePackageCallback =
new FakeRequestSurfacePackageCallback();
mSdkSandboxManager.requestSurfacePackage(
SDK_NAME_1,
getRequestSurfacePackageParams(),
Runnable::run,
surfacePackageCallback);
assertThat(surfacePackageCallback.isRequestSurfacePackageSuccessful()).isTrue();
}
/** Checks that {@code SdkSandbox.apk} only requests normal permissions in its manifest. */
// TODO: This should probably be a separate test module
@Test
public void testSdkSandboxPermissions() throws Exception {
final PackageManager pm =
InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
final PackageInfo sdkSandboxPackage =
pm.getPackageInfo(
pm.getSdkSandboxPackageName(),
PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS));
for (int i = 0; i < sdkSandboxPackage.requestedPermissions.length; i++) {
final String permissionName = sdkSandboxPackage.requestedPermissions[i];
final PermissionInfo permissionInfo = pm.getPermissionInfo(permissionName, 0);
expect.withMessage("SdkSandbox.apk requests non-normal permission %s", permissionName)
.that(permissionInfo.getProtection())
.isEqualTo(PermissionInfo.PROTECTION_NORMAL);
}
}
// TODO(b/244730098): The test below needs to be moved from e2e.
// It is not and e2e test.
@Test
public void testLoadSdkExceptionWriteToParcel() {
final Bundle bundle = new Bundle();
bundle.putChar("testKey", /*testValue=*/ 'C');
final String errorMessage = "Error Message";
final Exception cause = new Exception(errorMessage);
final LoadSdkException exception = new LoadSdkException(cause, bundle);
final Parcel parcel = Parcel.obtain();
exception.writeToParcel(parcel, /*flags=*/ 0);
// Create LoadSdkException with the same parcel
parcel.setDataPosition(0); // rewind
final LoadSdkException exceptionCheck = LoadSdkException.CREATOR.createFromParcel(parcel);
assertThat(exceptionCheck.getLoadSdkErrorCode()).isEqualTo(exception.getLoadSdkErrorCode());
assertThat(exceptionCheck.getMessage()).isEqualTo(exception.getMessage());
assertThat(exceptionCheck.getExtraInformation().getChar("testKey"))
.isEqualTo(exception.getExtraInformation().getChar("testKey"));
assertThat(exceptionCheck.getExtraInformation().keySet()).containsExactly("testKey");
}
// TODO(b/244730098): The test below needs to be moved from e2e.
// It is not and e2e test.
@Test
public void testLoadSdkExceptionDescribeContents() throws Exception {
final LoadSdkException exception = new LoadSdkException(new Exception(), new Bundle());
assertThat(exception.describeContents()).isEqualTo(0);
}
// TODO(b/244730098): The test below needs to be moved from e2e.
// It is not and e2e test.
@Test
public void testSandboxedSdkDescribeContents() throws Exception {
final SandboxedSdk sandboxedSdk = new SandboxedSdk(new Binder());
assertThat(sandboxedSdk.describeContents()).isEqualTo(0);
}
@Test
public void testSdkAndAppProcessImportanceIsAligned_AppIsBackgrounded() throws Exception {
// Sandbox and app priority is aligned only in U+.
assumeTrue(SdkLevel.isAtLeastU());
ICtsSdkProviderApi sdk = loadSdk();
assertThat(sdk.getProcessImportance()).isEqualTo(getAppProcessImportance());
// Move the app to the background.
mScenario.moveToState(Lifecycle.State.DESTROYED);
Thread.sleep(1000);
assertThat(sdk.getProcessImportance()).isEqualTo(getAppProcessImportance());
}
@Test
public void testSdkAndAppProcessImportanceIsAligned_AppIsBackgroundedAndForegrounded()
throws Exception {
// Sandbox and app priority is aligned only in U+.
assumeTrue(SdkLevel.isAtLeastU());
ICtsSdkProviderApi sdk = loadSdk();
assertThat(sdk.getProcessImportance()).isEqualTo(getAppProcessImportance());
// Move the app to the background and bring it back to the foreground again.
mScenario.recreate();
// The sandbox should have foreground importance again.
assertThat(sdk.getProcessImportance()).isEqualTo(getAppProcessImportance());
}
@Test
public void testSDKCanNotStartSandboxActivityDirectlyByAction() {
assumeTrue(SdkLevel.isAtLeastU());
final ICtsSdkProviderApi sdk = loadSdk();
assertThat(mScenario.getState()).isEqualTo(State.RESUMED);
SecurityException exception =
assertThrows(
SecurityException.class, () -> sdk.startSandboxActivityDirectlyByAction());
assertThat(exception.getMessage())
.isEqualTo("Sandbox process is not allowed to start sandbox activities.");
assertThat(mScenario.getState()).isEqualTo(State.RESUMED);
}
@Test
public void testSDKCanNotStartSandboxActivityDirectlyByComponent() {
assumeTrue(SdkLevel.isAtLeastU());
final ICtsSdkProviderApi sdk = loadSdk();
assertThat(mScenario.getState()).isEqualTo(State.RESUMED);
SecurityException exception =
assertThrows(
SecurityException.class,
() -> sdk.startSandboxActivityDirectlyByComponent());
assertThat(exception.getMessage())
.isEqualTo("Sandbox process is not allowed to start sandbox activities.");
assertThat(mScenario.getState()).isEqualTo(State.RESUMED);
}
@Test
public void testSandboxProcessShouldBeRunningToHostTheSandboxActivity() {
assumeTrue(SdkLevel.isAtLeastU());
assertThat(mScenario.getState()).isEqualTo(State.RESUMED);
mScenario.onActivity(
clientActivity -> {
SecurityException exception =
assertThrows(
SecurityException.class,
() ->
mSdkSandboxManager.startSdkSandboxActivity(
clientActivity, new Binder()));
assertThat(exception.getMessage())
.contains("There is no sandbox process running");
});
assertThat(mScenario.getState()).isEqualTo(State.RESUMED);
}
@Test
public void testStartSdkSandboxActivity() {
assumeTrue(SdkLevel.isAtLeastU());
ICtsSdkProviderApi sdk = loadSdk();
assertThat(mScenario.getState()).isEqualTo(State.RESUMED);
ActivityStarter activityStarter = new ActivityStarter();
assertThat(activityStarter.isActivityResumed()).isFalse();
startSandboxActivity(sdk, activityStarter);
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(activityStarter.isActivityResumed()).isTrue();
}
@Test
public void testStartSdkSandboxActivityOnTopOfASandboxActivity() {
assumeTrue(SdkLevel.isAtLeastU());
ICtsSdkProviderApi sdk = loadSdk();
ActivityStarter sandboxActivity1Starter = new ActivityStarter();
ActivityStarter sandboxActivity2Starter = new ActivityStarter();
assertThat(sandboxActivity1Starter.isActivityResumed()).isFalse();
assertThat(sandboxActivity2Starter.isActivityResumed()).isFalse();
assertThat(mScenario.getState()).isEqualTo(State.RESUMED);
startSandboxActivity(sdk, sandboxActivity1Starter);
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(sandboxActivity1Starter.isActivityResumed()).isTrue();
assertThat(sandboxActivity2Starter.isActivityResumed()).isFalse();
startSandboxActivity(sdk, sandboxActivity2Starter);
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(sandboxActivity1Starter.isActivityResumed()).isFalse();
assertThat(sandboxActivity2Starter.isActivityResumed()).isTrue();
}
@Test
public void testStartLocalActivityOnTopOfASandboxActivity() {
assumeTrue(SdkLevel.isAtLeastU());
ICtsSdkProviderApi sdk = loadSdk();
ActivityStarter sandboxActivityStarter = new ActivityStarter();
ActivityStarter otherClientActivityStarter = new ActivityStarter();
assertThat(sandboxActivityStarter.isActivityResumed()).isFalse();
assertThat(otherClientActivityStarter.isActivityResumed()).isFalse();
assertThat(mScenario.getState()).isEqualTo(State.RESUMED);
startSandboxActivity(sdk, sandboxActivityStarter);
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(otherClientActivityStarter.isActivityResumed()).isFalse();
assertThat(sandboxActivityStarter.isActivityResumed()).isTrue();
mScenario.onActivity(
clientActivity -> {
otherClientActivityStarter.setFromActivity(clientActivity);
});
otherClientActivityStarter.startLocalActivity();
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(sandboxActivityStarter.isActivityResumed()).isFalse();
assertThat(otherClientActivityStarter.isActivityResumed()).isTrue();
}
@Test
public void testClientAppCanClearTopWhileOtherActivitiesOnTopIncludingSandboxActivities() {
assumeTrue(SdkLevel.isAtLeastU());
ICtsSdkProviderApi sdk = loadSdk();
// Start 2 sandbox activities.
ActivityStarter sandboxActivity1Starter = new ActivityStarter();
ActivityStarter sandboxActivity2Starter = new ActivityStarter();
startSandboxActivity(sdk, sandboxActivity1Starter);
startSandboxActivity(sdk, sandboxActivity2Starter);
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(sandboxActivity1Starter.isActivityResumed()).isFalse();
assertThat(sandboxActivity2Starter.isActivityResumed()).isTrue();
// Clear top (include the sandbox activities on top).
ActivityStarter clearTopActivityStarter = new ActivityStarter();
mScenario.onActivity(
clientActivity -> {
clearTopActivityStarter.setFromActivity(clientActivity);
});
clearTopActivityStarter.startLocalActivity(Intent.FLAG_ACTIVITY_CLEAR_TOP);
assertThat(sandboxActivity1Starter.isActivityResumed()).isFalse();
assertThat(sandboxActivity2Starter.isActivityResumed()).isFalse();
assertThat(clearTopActivityStarter.isActivityResumed()).isTrue();
}
@Test
@Ignore("b/298171583")
public void testBackNavigation() throws Exception {
assumeTrue(SdkLevel.isAtLeastU());
ICtsSdkProviderApi sdk = loadSdk();
ActivityStarter sandboxActivityStarter = new ActivityStarter();
IActivityActionExecutor actionExecutor = startSandboxActivity(sdk, sandboxActivityStarter);
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(sandboxActivityStarter.isActivityResumed()).isTrue();
try {
actionExecutor.disableBackButton();
} catch (RemoteException e) {
fail("Error while disabling back button: " + e.getMessage());
}
sUiDevice.pressBack();
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(sandboxActivityStarter.isActivityResumed()).isTrue();
try {
actionExecutor.enableBackButton();
} catch (RemoteException e) {
fail("Error while enabling back button: " + e.getMessage());
}
sUiDevice.pressBack();
assertThat(mScenario.getState()).isEqualTo(State.RESUMED);
assertThat(sandboxActivityStarter.isActivityResumed()).isFalse();
}
@Test
@Ignore("b/298171583")
public void testSandboxActivityShouldRotateIfNotLocked() throws Exception {
assumeTrue(SdkLevel.isAtLeastU());
ICtsSdkProviderApi sdk = loadSdk();
ActivityStarter sandboxActivityStarter = new ActivityStarter();
startSandboxActivity(sdk, sandboxActivityStarter);
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(sandboxActivityStarter.isActivityResumed()).isTrue();
// Assert Natural Rotation.
assertThat(sUiDevice.getDisplayRotation()).isEqualTo(Surface.ROTATION_0);
// Rotate device to landscape
sUiDevice.setOrientationLandscape();
// Without SDK locking the orientation, display should rotate.
assertThat(sUiDevice.getDisplayRotation()).isEqualTo(Surface.ROTATION_90);
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(sandboxActivityStarter.isActivityResumed()).isTrue();
}
@Test
@Ignore("b/298171583")
public void testSandboxActivityOrientationLocking() throws Exception {
assumeTrue(SdkLevel.isAtLeastU());
ICtsSdkProviderApi sdk = loadSdk();
ActivityStarter sandboxActivityStarter = new ActivityStarter();
IActivityActionExecutor actionExecutor = startSandboxActivity(sdk, sandboxActivityStarter);
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(sandboxActivityStarter.isActivityResumed()).isTrue();
// Assert Natural Rotation.
assertThat(sUiDevice.getDisplayRotation()).isEqualTo(Surface.ROTATION_0);
// Locking orientation to landscape should override device current orientation
// (portrait) and restart the sandbox activity in the landscape orientation.
actionExecutor.setOrientationToLandscape();
assertThat(sUiDevice.getDisplayRotation()).isEqualTo(Surface.ROTATION_90);
// Rotation the device should not affect the locked display orientation.
sUiDevice.setOrientationPortrait();
assertThat(sUiDevice.getDisplayRotation()).isEqualTo(Surface.ROTATION_90);
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(sandboxActivityStarter.isActivityResumed()).isTrue();
// Locking orientation to portrait should override device current orientation
// (landscape) and restart the sandbox activity in the portrait orientation.
actionExecutor.setOrientationToPortrait();
assertThat(sUiDevice.getDisplayRotation()).isEqualTo(Surface.ROTATION_0);
// Rotation the device should not affect the locked display orientation.
sUiDevice.setOrientationLandscape();
assertThat(sUiDevice.getDisplayRotation()).isEqualTo(Surface.ROTATION_0);
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(sandboxActivityStarter.isActivityResumed()).isTrue();
}
@Test
public void testStartSdkSandboxedActivityFailIfTheHandlerUnregistered() {
assumeTrue(SdkLevel.isAtLeastU());
// Load SDK in sandbox
ICtsSdkProviderApi sdk = loadSdk();
ActivityStarter activityStarter = new ActivityStarter();
assertThat(mScenario.getState()).isEqualTo(State.RESUMED);
Bundle extras = new Bundle();
extras.putBoolean(UNREGISTER_BEFORE_STARTING_KEY, true);
startSandboxActivity(sdk, activityStarter, extras);
assertThat(mScenario.getState()).isEqualTo(State.RESUMED);
assertThat(activityStarter.isActivityResumed()).isFalse();
}
@Test
public void testSandboxActivityStartIntentViewWithNoSecurityExceptions() throws Exception {
assumeTrue(SdkLevel.isAtLeastU());
ICtsSdkProviderApi sdk = loadSdk();
ActivityStarter sandboxActivityStarter = new ActivityStarter();
IActivityActionExecutor actionExecutor = startSandboxActivity(sdk, sandboxActivityStarter);
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(sandboxActivityStarter.isActivityResumed()).isTrue();
actionExecutor.openLandingPage();
}
/**
* Ensure that SDK can finish the sandbox activity.
*
* @throws RemoteException
*/
@Test
public void testSdkCanFinishSandboxActivity() throws RemoteException {
assumeTrue(SdkLevel.isAtLeastU());
ICtsSdkProviderApi sdk = loadSdk();
ActivityStarter sandboxActivityStarter = new ActivityStarter();
IActivityActionExecutor actionExecutor = startSandboxActivity(sdk, sandboxActivityStarter);
assertThat(mScenario.getState()).isIn(Arrays.asList(State.CREATED, State.STARTED));
assertThat(sandboxActivityStarter.isActivityResumed()).isTrue();
actionExecutor.finish();
assertTrue(
sUiDevice.wait(Until.hasObject(By.text("DEFAULT_SHOW_TEXT")), WAIT_FOR_TEXT_IN_MS));
assertThat(mScenario.getState()).isEqualTo(State.RESUMED);
assertThat(sandboxActivityStarter.isActivityResumed()).isFalse();
}
// Helper method to load SDK_NAME_1
private ICtsSdkProviderApi loadSdk() {
final FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_1, new Bundle(), Runnable::run, callback);
callback.assertLoadSdkIsSuccessful();
final SandboxedSdk sandboxedSdk = callback.getSandboxedSdk();
assertNotNull(sandboxedSdk);
return ICtsSdkProviderApi.Stub.asInterface(callback.getSandboxedSdk().getInterface());
}
private int getAppProcessImportance() {
ActivityManager.RunningAppProcessInfo processInfo =
new ActivityManager.RunningAppProcessInfo();
ActivityManager.getMyMemoryState(processInfo);
return processInfo.importance;
}
private IActivityActionExecutor startSandboxActivity(
ICtsSdkProviderApi sdk, ActivityStarter activityStarter) {
return startSandboxActivity(sdk, activityStarter, new Bundle());
}
private IActivityActionExecutor startSandboxActivity(
ICtsSdkProviderApi sdk, ActivityStarter activityStarter, Bundle extras) {
final String randomText = mRandom.nextInt(Integer.MAX_VALUE) + "";
extras.putString(TEXT_KEY, randomText);
ActivityExecutorContainer activityExecutorContainer = new ActivityExecutorContainer();
mScenario.onActivity(
clientActivity -> {
activityStarter.setFromActivity(clientActivity);
IActivityActionExecutor actionExecutor = null;
try {
actionExecutor =
(IActivityActionExecutor)
sdk.startActivity(activityStarter, extras);
} catch (RemoteException e) {
fail("Got exception while starting activity: " + e.getMessage());
}
activityExecutorContainer.setExecutor(actionExecutor);
});
IActivityActionExecutor actionExecutor = activityExecutorContainer.getExecutor();
assertThat(actionExecutor).isNotNull();
if (extras.containsKey(UNREGISTER_BEFORE_STARTING_KEY)) {
assertFalse(sUiDevice.wait(Until.hasObject(By.text(randomText)), WAIT_FOR_TEXT_IN_MS));
} else {
assertTrue(sUiDevice.wait(Until.hasObject(By.text(randomText)), WAIT_FOR_TEXT_IN_MS));
}
return actionExecutor;
}
// Separate class to store IActivityActionExecutor which is returned in a lambda expression.
private static class ActivityExecutorContainer {
private IActivityActionExecutor mExecutor;
public void setExecutor(IActivityActionExecutor executor) {
mExecutor = executor;
}
public IActivityActionExecutor getExecutor() {
return mExecutor;
}
}
private class ActivityStarter extends IActivityStarter.Stub {
private Activity mFromActivity;
private boolean mActivityResumed = false;
ActivityStarter() {}
// To be called by SDKs to start sandbox activities.
@Override
public void startSdkSandboxActivity(IBinder token) throws RemoteException {
assertThat(mFromActivity).isNotNull();
mSdkSandboxManager.startSdkSandboxActivity(mFromActivity, token);
}
// It is called to notify that onResume() is called against the new started Activity.
@Override
public void onActivityResumed() {
mActivityResumed = true;
}
// It is called to notify the new started Activity is no longer in the Resumed state.
@Override
public void onLeftActivityResumed() {
mActivityResumed = false;
}
// To start local test activities (can not be called between processes).
public void startLocalActivity() {
assertThat(mFromActivity).isNotNull();
startLocalActivity(0);
}
// To start local test activities (can not be called between processes).
public void startLocalActivity(int flags) {
assertThat(mFromActivity).isNotNull();
final Intent intent = new Intent(mFromActivity, TestActivity.class);
final Bundle params = new Bundle();
final String randomText = mRandom.nextInt(Integer.MAX_VALUE) + "";
params.putString(TEXT_KEY, randomText);
params.putBinder(ACTIVITY_STARTER_KEY, this);
intent.putExtras(params);
intent.addFlags(flags);
mFromActivity.startActivity(intent);
assertTrue(sUiDevice.wait(Until.hasObject(By.text(randomText)), WAIT_FOR_TEXT_IN_MS));
}
public void setFromActivity(Activity activity) {
mFromActivity = activity;
}
public boolean isActivityResumed() {
return mActivityResumed;
}
}
private Bundle getRequestSurfacePackageParams() {
Bundle params = new Bundle();
params.putInt(EXTRA_WIDTH_IN_PIXELS, 500);
params.putInt(EXTRA_HEIGHT_IN_PIXELS, 500);
params.putInt(EXTRA_DISPLAY_ID, 0);
params.putBinder(EXTRA_HOST_TOKEN, new Binder());
return params;
}
private void loadMultipleSdks() {
FakeLoadSdkCallback callback = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_1, new Bundle(), Runnable::run, callback);
callback.assertLoadSdkIsSuccessful();
FakeLoadSdkCallback callback2 = new FakeLoadSdkCallback();
mSdkSandboxManager.loadSdk(SDK_NAME_2, new Bundle(), Runnable::run, callback2);
callback2.assertLoadSdkIsSuccessful();
}
// Returns true if the sandbox was already likely existing, false otherwise.
private boolean killSandboxIfExists() throws Exception {
FakeSdkSandboxProcessDeathCallback callback = new FakeSdkSandboxProcessDeathCallback();
mSdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run, callback);
killSandbox();
return callback.waitForSandboxDeath();
}
private void killSandbox() throws Exception {
// TODO(b/241542162): Avoid using reflection as a workaround once test apis can be run
// without issue.
mSdkSandboxManager.getClass().getMethod("stopSdkSandbox").invoke(mSdkSandboxManager);
}
}