blob: b2654ee2a9504eb68497a91490b66c863111e984 [file] [log] [blame]
/*
* Copyright (C) 2019 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.server.wm;
import static android.server.wm.ComponentNameUtils.getActivityName;
import static android.server.wm.StateLogger.logAlways;
import static android.server.wm.app.Components.DISPLAY_ACCESS_CHECK_EMBEDDING_ACTIVITY;
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
import static android.server.wm.app.Components.LAUNCH_BROADCAST_RECEIVER;
import static android.server.wm.app.Components.LaunchBroadcastReceiver.ACTION_TEST_ACTIVITY_START;
import static android.server.wm.app.Components.LaunchBroadcastReceiver.EXTRA_COMPONENT_NAME;
import static android.server.wm.app.Components.LaunchBroadcastReceiver.EXTRA_TARGET_DISPLAY;
import static android.server.wm.app.Components.LaunchBroadcastReceiver.LAUNCH_BROADCAST_ACTION;
import static android.server.wm.app.Components.TEST_ACTIVITY;
import static android.server.wm.app.Components.VIRTUAL_DISPLAY_ACTIVITY;
import static android.server.wm.second.Components.EMBEDDING_ACTIVITY;
import static android.server.wm.second.Components.EmbeddingActivity.ACTION_EMBEDDING_TEST_ACTIVITY_START;
import static android.server.wm.second.Components.EmbeddingActivity.EXTRA_EMBEDDING_COMPONENT_NAME;
import static android.server.wm.second.Components.EmbeddingActivity.EXTRA_EMBEDDING_TARGET_DISPLAY;
import static android.server.wm.second.Components.SECOND_ACTIVITY;
import static android.server.wm.second.Components.SECOND_LAUNCH_BROADCAST_ACTION;
import static android.server.wm.second.Components.SECOND_LAUNCH_BROADCAST_RECEIVER;
import static android.server.wm.second.Components.SECOND_NO_EMBEDDING_ACTIVITY;
import static android.server.wm.second.Components.SecondActivity.EXTRA_DISPLAY_ACCESS_CHECK;
import static android.server.wm.third.Components.THIRD_ACTIVITY;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.server.wm.ActivityManagerState.ActivityDisplay;
import android.server.wm.ActivityManagerState.ActivityStack;
import android.server.wm.CommandSession.ActivitySession;
import android.util.SparseArray;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import androidx.test.filters.FlakyTest;
import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.TestUtils;
import org.junit.Before;
import org.junit.Test;
/**
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:MultiDisplaySecurityTests
*
* Tests if be allowed to launch an activity on multi-display environment.
*/
@Presubmit
public class MultiDisplaySecurityTests extends MultiDisplayTestBase {
@Before
@Override
public void setUp() throws Exception {
super.setUp();
assumeTrue(supportsMultiDisplay());
}
/**
* Tests launching an activity on a virtual display without special permission must be allowed
* for activities with same UID.
*/
@Test
public void testLaunchWithoutPermissionOnVirtualDisplayByOwner() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new virtual display.
final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
// Try to launch an activity and check it security exception was triggered.
getLaunchActivityBuilder()
.setUseBroadcastReceiver(LAUNCH_BROADCAST_RECEIVER, LAUNCH_BROADCAST_ACTION)
.setDisplayId(newDisplay.mId)
.setTargetActivity(TEST_ACTIVITY)
.execute();
mAmWmState.waitForValidState(TEST_ACTIVITY);
final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
final ActivityStack focusedStack =
mAmWmState.getAmState().getStackById(externalFocusedStackId);
assertEquals("Focused stack must be on secondary display", newDisplay.mId,
focusedStack.mDisplayId);
mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
TEST_ACTIVITY);
assertEquals("Activity launched by owner must be on external display",
externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
}
}
/**
* Tests launching an activity on a virtual display without special permission must not be
* allowed.
*/
@Test
public void testLaunchWithoutPermissionOnVirtualDisplay() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new virtual display.
final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
separateTestJournal();
// Try to launch an activity and check it security exception was triggered.
getLaunchActivityBuilder()
.setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
SECOND_LAUNCH_BROADCAST_ACTION)
.setDisplayId(newDisplay.mId)
.setTargetActivity(TEST_ACTIVITY)
.execute();
assertSecurityExceptionFromActivityLauncher();
mAmWmState.computeState(TEST_ACTIVITY);
assertFalse("Restricted activity must not be launched",
mAmWmState.getAmState().containsActivity(TEST_ACTIVITY));
}
}
/**
* Tests launching an activity on virtual display and then launching another activity that
* doesn't allow embedding - it should fail with security exception.
*/
@Test
public void testConsequentLaunchActivityFromVirtualDisplayNoEmbedding() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new virtual display.
final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
// Launch activity on new secondary display.
launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
"Activity launched on secondary display must be resumed");
separateTestJournal();
// Launch second activity from app on secondary display specifying same display id.
getLaunchActivityBuilder()
.setTargetActivity(SECOND_NO_EMBEDDING_ACTIVITY)
.setDisplayId(newDisplay.mId)
.execute();
assertSecurityExceptionFromActivityLauncher();
}
}
/**
* Tests
* {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
* for simulated display. It is owned by system and is public, so should be accessible.
*/
@Test
public void testCanAccessSystemOwnedDisplay() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
.createDisplay();
final ActivityManager activityManager =
(ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(TEST_ACTIVITY);
assertTrue(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
newDisplay.mId, intent));
}
}
/**
* Tests
* {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
* for a public virtual display and an activity that doesn't support embedding from shell.
*/
@Test
public void testCanAccessPublicVirtualDisplayWithInternalPermission() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
.createDisplay();
final ActivityManager activityManager =
(ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
final Intent intent = new Intent(Intent.ACTION_VIEW)
.setComponent(SECOND_NO_EMBEDDING_ACTIVITY);
SystemUtil.runWithShellPermissionIdentity(() ->
assertTrue(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
newDisplay.mId, intent)), "android.permission.INTERNAL_SYSTEM_WINDOW");
}
}
/**
* Tests
* {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
* for a private virtual display and an activity that doesn't support embedding from shell.
*/
@Test
public void testCanAccessPrivateVirtualDisplayWithInternalPermission() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
.createDisplay();
final ActivityManager activityManager =
(ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
final Intent intent = new Intent(Intent.ACTION_VIEW)
.setComponent(SECOND_NO_EMBEDDING_ACTIVITY);
SystemUtil.runWithShellPermissionIdentity(() ->
assertTrue(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
newDisplay.mId, intent)), "android.permission.INTERNAL_SYSTEM_WINDOW");
}
}
/**
* Tests
* {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
* for a public virtual display, an activity that supports embedding but the launching entity
* does not have required permission to embed an activity from other app.
*/
@Test
public void testCantAccessPublicVirtualDisplayNoEmbeddingPermission() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
.createDisplay();
final ActivityManager activityManager =
(ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(SECOND_ACTIVITY);
assertFalse(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
newDisplay.mId, intent));
}
}
/**
* Tests
* {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
* for a public virtual display and an activity that does not support embedding.
*/
@Test
public void testCantAccessPublicVirtualDisplayActivityEmbeddingNotAllowed() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
.createDisplay();
final ActivityManager activityManager =
(ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
final Intent intent = new Intent(Intent.ACTION_VIEW)
.setComponent(SECOND_NO_EMBEDDING_ACTIVITY);
SystemUtil.runWithShellPermissionIdentity(() ->
assertFalse(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
newDisplay.mId, intent)), "android.permission.ACTIVITY_EMBEDDING");
}
}
/**
* Tests
* {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
* for a public virtual display and an activity that supports embedding.
*/
@Test
public void testCanAccessPublicVirtualDisplayActivityEmbeddingAllowed() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true)
.createDisplay();
final ActivityManager activityManager =
(ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
final Intent intent = new Intent(Intent.ACTION_VIEW)
.setComponent(SECOND_ACTIVITY);
SystemUtil.runWithShellPermissionIdentity(() ->
assertTrue(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
newDisplay.mId, intent)), "android.permission.ACTIVITY_EMBEDDING");
}
}
/**
* Tests
* {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
* for a private virtual display.
*/
@Test
public void testCantAccessPrivateVirtualDisplay() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
.createDisplay();
final ActivityManager activityManager =
(ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(SECOND_ACTIVITY);
assertFalse(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
newDisplay.mId, intent));
}
}
/**
* Tests
* {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
* for a private virtual display to check the start of its own activity.
*/
@Test
public void testCanAccessPrivateVirtualDisplayByOwner() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
.createDisplay();
// Check the embedding call
separateTestJournal();
mContext.sendBroadcast(new Intent(ACTION_TEST_ACTIVITY_START)
.setPackage(LAUNCH_BROADCAST_RECEIVER.getPackageName())
.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
.putExtra(EXTRA_COMPONENT_NAME, TEST_ACTIVITY)
.putExtra(EXTRA_TARGET_DISPLAY, newDisplay.mId));
assertActivityStartCheckResult(true);
}
}
/**
* Tests
* {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
* for a private virtual display by UID present on that display and target activity that allows
* embedding.
*/
@Test
public void testCanAccessPrivateVirtualDisplayByUidPresentOnDisplayActivityEmbeddingAllowed()
throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
.createDisplay();
// Launch a test activity into the target display
launchActivityOnDisplay(EMBEDDING_ACTIVITY, newDisplay.mId);
// Check the embedding call
separateTestJournal();
mContext.sendBroadcast(new Intent(ACTION_EMBEDDING_TEST_ACTIVITY_START)
.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
.putExtra(EXTRA_EMBEDDING_COMPONENT_NAME, SECOND_ACTIVITY)
.putExtra(EXTRA_EMBEDDING_TARGET_DISPLAY, newDisplay.mId));
assertActivityStartCheckResult(true);
}
}
/**
* Tests
* {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
* for a private virtual display by UID present on that display and target activity that does
* not allow embedding.
*/
@Test
public void testCanAccessPrivateVirtualDisplayByUidPresentOnDisplayActivityEmbeddingNotAllowed()
throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false)
.createDisplay();
// Launch a test activity into the target display
launchActivityOnDisplay(EMBEDDING_ACTIVITY, newDisplay.mId);
// Check the embedding call
separateTestJournal();
mContext.sendBroadcast(new Intent(ACTION_EMBEDDING_TEST_ACTIVITY_START)
.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
.putExtra(EXTRA_EMBEDDING_COMPONENT_NAME, SECOND_NO_EMBEDDING_ACTIVITY)
.putExtra(EXTRA_EMBEDDING_TARGET_DISPLAY, newDisplay.mId));
assertActivityStartCheckResult(false);
}
}
private void assertActivityStartCheckResult(boolean expected) {
final String component = ActivityLauncher.TAG;
for (int retry = 1; retry <= 5; retry++) {
final Bundle extras = TestJournalProvider.TestJournalContainer.get(component).extras;
if (extras.containsKey(ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY)) {
assertEquals("Activity start check must match", expected, extras
.getBoolean(ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY));
return;
}
logAlways("***Waiting for activity start check for " + component
+ " ... retry=" + retry);
SystemClock.sleep(500);
}
fail("Expected activity start check from " + component + " not found");
}
@Test
public void testDisplayHasAccess_UIDCanPresentOnPrivateDisplay() throws Exception {
try (final VirtualDisplayLauncher virtualDisplayLauncher = new VirtualDisplayLauncher()) {
// Create a virtual private display.
final ActivityDisplay newDisplay = virtualDisplayLauncher
.setPublicDisplay(false)
.createDisplay();
// Launch an embeddable activity into the private display.
// Assert that the UID can present on display.
final ActivitySession session1 = virtualDisplayLauncher.launchActivityOnDisplay(
DISPLAY_ACCESS_CHECK_EMBEDDING_ACTIVITY, newDisplay);
assertEquals("Activity which the UID should accessible on private display",
isUidAccesibleOnDisplay(session1), true);
// Launch another embeddable activity with a different UID, verify that it will be
// able to access the display where it was put.
// Note that set withShellPermission as true in launchActivityOnDisplay is to
// make sure ACTIVITY_EMBEDDING can be granted by shell.
final ActivitySession session2 = virtualDisplayLauncher.launchActivityOnDisplay(
SECOND_ACTIVITY, newDisplay,
bundle -> bundle.putBoolean(EXTRA_DISPLAY_ACCESS_CHECK, true),
true /* withShellPermission */, true /* waitForLaunch */);
// Verify SECOND_ACTIVITY's UID has access to this virtual private display.
assertEquals("Second activity which the UID should accessible on private display",
isUidAccesibleOnDisplay(session2), true);
}
}
@Test
public void testDisplayHasAccess_NoAccessWhenUIDNotPresentOnPrivateDisplay() throws Exception {
try (final VirtualDisplayLauncher virtualDisplayLauncher = new VirtualDisplayLauncher()) {
// Create a virtual private display.
final ActivityDisplay newDisplay = virtualDisplayLauncher
.setPublicDisplay(false)
.createDisplay();
// Launch an embeddable activity into the private display.
// Assume that the UID can access on display.
final ActivitySession session1 = virtualDisplayLauncher.launchActivityOnDisplay(
DISPLAY_ACCESS_CHECK_EMBEDDING_ACTIVITY, newDisplay);
assertEquals("Activity which the UID should accessible on private display",
isUidAccesibleOnDisplay(session1), true);
// Verify SECOND_NO_EMBEDDING_ACTIVITY's UID can't access this virtual private display
// since there is no entity with this UID on this display.
// Note that set withShellPermission as false in launchActivityOnDisplay is to
// prevent activity can launch when INTERNAL_SYSTEM_WINDOW granted by shell case.
separateTestJournal();
final ActivitySession session2 = virtualDisplayLauncher.launchActivityOnDisplay(
SECOND_NO_EMBEDDING_ACTIVITY, newDisplay, null /* extrasConsumer */,
false /* withShellPermission */, false /* waitForLaunch */);
assertEquals("Second activity which the UID should not accessible on private display",
isUidAccesibleOnDisplay(session2), false);
}
}
@Test
public void testDisplayHasAccess_ExceptionWhenAddViewWithoutPresentOnPrivateDisplay()
throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create a virtual private display.
final ActivityDisplay newDisplay = virtualDisplaySession
.setPublicDisplay(false)
.createDisplay();
try {
final Display display = mContext.getSystemService(DisplayManager.class).getDisplay(
newDisplay.mId);
final Context newDisplayContext = mContext.createDisplayContext(display);
newDisplayContext.getSystemService(WindowManager.class).addView(new View(mContext),
new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
} catch (IllegalArgumentException e) {
// Exception happened when createDisplayContext with invalid display.
return;
}
fail("UID should not have access to private display without present entities.");
}
}
private boolean isUidAccesibleOnDisplay(ActivitySession session) {
boolean result = false;
try {
result = session.isUidAccesibleOnDisplay();
} catch (RuntimeException e) {
// Catch the exception while waiting reply (i.e. timeout)
}
return result;
}
/** Test that shell is allowed to launch on secondary displays. */
@Test
public void testPermissionLaunchFromShell() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new virtual display.
final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
mAmWmState.assertFocusedActivity("Virtual display activity must be on top",
VIRTUAL_DISPLAY_ACTIVITY);
final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
ActivityStack frontStack = mAmWmState.getAmState().getStackById(
defaultDisplayFocusedStackId);
assertEquals("Top stack must remain on primary display",
DEFAULT_DISPLAY, frontStack.mDisplayId);
// Launch activity on new secondary display.
launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
"Front activity must be on secondary display");
mAmWmState.assertResumedActivities("Both displays must have resumed activities",
new SparseArray<ComponentName>(){{
put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY);
put(newDisplay.mId, TEST_ACTIVITY);
}}
);
// Launch other activity with different uid and check it is launched on dynamic stack on
// secondary display.
final String startCmd = "am start -n " + getActivityName(SECOND_ACTIVITY)
+ " --display " + newDisplay.mId;
executeShellCommand(startCmd);
waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
"Focus must be on newly launched app");
mAmWmState.assertResumedActivities("Both displays must have resumed activities",
new SparseArray<ComponentName>(){{
put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY);
put(newDisplay.mId, SECOND_ACTIVITY);
}}
);
}
}
/** Test that launching from app that is on external display is allowed. */
@Test
public void testPermissionLaunchFromAppOnSecondary() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new simulated display.
final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
.createDisplay();
// Launch activity with different uid on secondary display.
final String startCmd = "am start -n " + getActivityName(SECOND_ACTIVITY)
+ " --display " + newDisplay.mId;
executeShellCommand(startCmd);
waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
"Top activity must be the newly launched one");
// Launch another activity with third different uid from app on secondary display and
// check it is launched on secondary display.
getLaunchActivityBuilder()
.setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
SECOND_LAUNCH_BROADCAST_ACTION)
.setDisplayId(newDisplay.mId)
.setTargetActivity(THIRD_ACTIVITY)
.execute();
waitAndAssertTopResumedActivity(THIRD_ACTIVITY, newDisplay.mId,
"Top activity must be the newly launched one");
}
}
/** Tests that an activity can launch an activity from a different UID into its own task. */
@Test
public void testPermissionLaunchMultiUidTask() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
.createDisplay();
launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
mAmWmState.computeState(LAUNCHING_ACTIVITY);
// Check that the first activity is launched onto the secondary display
final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
ActivityStack frontStack = mAmWmState.getAmState().getStackById(
frontStackId);
assertEquals("Activity launched on secondary display must be resumed",
getActivityName(LAUNCHING_ACTIVITY),
frontStack.mResumedActivity);
mAmWmState.assertFocusedStack("Top stack must be on secondary display",
frontStackId);
// Launch an activity from a different UID into the first activity's task
getLaunchActivityBuilder().setTargetActivity(SECOND_ACTIVITY).execute();
waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
"Top activity must be the newly launched one");
frontStack = mAmWmState.getAmState().getStackById(frontStackId);
assertEquals("Secondary display must contain 1 task", 1, frontStack.getTasks().size());
}
}
/**
* Test that launching from display owner is allowed even when the the display owner
* doesn't have anything on the display.
*/
@Test
public void testPermissionLaunchFromOwner() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new virtual display.
final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
VIRTUAL_DISPLAY_ACTIVITY);
final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
ActivityStack frontStack =
mAmWmState.getAmState().getStackById(defaultDisplayFocusedStackId);
assertEquals("Top stack must remain on primary display",
DEFAULT_DISPLAY, frontStack.mDisplayId);
// Launch other activity with different uid on secondary display.
final String startCmd = "am start -n " + getActivityName(SECOND_ACTIVITY)
+ " --display " + newDisplay.mId;
executeShellCommand(startCmd);
waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId,
"Top activity must be the newly launched one");
// Check that owner uid can launch its own activity on secondary display.
getLaunchActivityBuilder()
.setUseBroadcastReceiver(LAUNCH_BROADCAST_RECEIVER, LAUNCH_BROADCAST_ACTION)
.setNewTask(true)
.setMultipleTask(true)
.setDisplayId(newDisplay.mId)
.execute();
waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
"Top activity must be the newly launched one");
}
}
/**
* Test that launching from app that is not present on external display and doesn't own it to
* that external display is not allowed.
*/
@Test
public void testPermissionLaunchFromDifferentApp() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new virtual display.
final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
VIRTUAL_DISPLAY_ACTIVITY);
final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
ActivityStack frontStack = mAmWmState.getAmState().getStackById(
defaultDisplayFocusedStackId);
assertEquals("Top stack must remain on primary display",
DEFAULT_DISPLAY, frontStack.mDisplayId);
// Launch activity on new secondary display.
launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
"Top activity must be the newly launched one");
separateTestJournal();
// Launch other activity with different uid and check security exception is triggered.
getLaunchActivityBuilder()
.setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER,
SECOND_LAUNCH_BROADCAST_ACTION)
.setDisplayId(newDisplay.mId)
.setTargetActivity(THIRD_ACTIVITY)
.execute();
assertSecurityExceptionFromActivityLauncher();
mAmWmState.waitForValidState(TEST_ACTIVITY);
mAmWmState.assertFocusedActivity("Top activity must be the first one launched",
TEST_ACTIVITY);
}
}
private void assertSecurityExceptionFromActivityLauncher() {
final String component = ActivityLauncher.TAG;
for (int retry = 1; retry <= 5; retry++) {
if (ActivityLauncher.hasCaughtSecurityException()) {
return;
}
logAlways("***Waiting for SecurityException from " + component + " ... retry=" + retry);
SystemClock.sleep(500);
}
fail("Expected exception from " + component + " not found");
}
/**
* Test that only private virtual display can show content with insecure keyguard.
*/
@Test
public void testFlagShowWithInsecureKeyguardOnPublicVirtualDisplay() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Try to create new show-with-insecure-keyguard public virtual display.
final ActivityDisplay newDisplay = virtualDisplaySession
.setPublicDisplay(true)
.setCanShowWithInsecureKeyguard(true)
.setMustBeCreated(false)
.createDisplay();
// Check that the display is not created.
assertNull(newDisplay);
}
}
/**
* Test setting system decoration flag and show IME flag without sufficient permissions.
*/
@Test
@FlakyTest(bugId = 130284250)
public void testSettingFlagWithoutInternalSystemPermission() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// The reason to use a trusted display is that we can guarantee the security exception
// is coming from lacking internal system permission.
final ActivityDisplay trustedDisplay = virtualDisplaySession
.setSimulateDisplay(true).createDisplay();
final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
// Verify setting system decorations flag without internal system permission.
try {
wm.setShouldShowSystemDecors(trustedDisplay.mId, true);
// Unexpected result, restore flag to avoid affecting other tests.
wm.setShouldShowSystemDecors(trustedDisplay.mId, false);
TestUtils.waitUntil("Waiting for system decoration flag to be set",
5 /* timeoutSecond */,
() -> !wm.shouldShowSystemDecors(trustedDisplay.mId));
fail("Should not allow setting system decoration flag without internal system "
+ "permission");
} catch (SecurityException e) {
// Expected security exception.
}
// Verify setting show IME flag without internal system permission.
try {
wm.setShouldShowIme(trustedDisplay.mId, true);
// Unexpected result, restore flag to avoid affecting other tests.
wm.setShouldShowIme(trustedDisplay.mId, false);
TestUtils.waitUntil("Waiting for show IME flag to be set",
5 /* timeoutSecond */,
() -> !wm.shouldShowIme(trustedDisplay.mId));
fail("Should not allow setting show IME flag without internal system permission");
} catch (SecurityException e) {
// Expected security exception.
}
}
}
/**
* Test getting system decoration flag and show IME flag without sufficient permissions.
*/
@Test
@FlakyTest(bugId = 130284250)
public void testGettingFlagWithoutInternalSystemPermission() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// The reason to use a trusted display is that we can guarantee the security exception
// is coming from lacking internal system permission.
final ActivityDisplay trustedDisplay = virtualDisplaySession
.setSimulateDisplay(true).createDisplay();
final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
// Verify getting system decorations flag without internal system permission.
try {
wm.shouldShowSystemDecors(trustedDisplay.mId);
fail("Only allow internal system to get system decoration flag");
} catch (SecurityException e) {
// Expected security exception.
}
// Verify getting show IME flag without internal system permission.
try {
wm.shouldShowIme(trustedDisplay.mId);
fail("Only allow internal system to get show IME flag");
} catch (SecurityException e) {
// Expected security exception.
}
}
}
/**
* Test setting system decoration flag and show IME flag to the untrusted display.
*/
@Test
@FlakyTest(bugId = 130284250)
public void testSettingFlagToUntrustedDisplay() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay untrustedDisplay = virtualDisplaySession.createDisplay();
final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
// Verify setting system decoration flag to an untrusted display.
getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
try {
wm.setShouldShowSystemDecors(untrustedDisplay.mId, true);
// Unexpected result, restore flag to avoid affecting other tests.
wm.setShouldShowSystemDecors(untrustedDisplay.mId, false);
TestUtils.waitUntil("Waiting for system decoration flag to be set",
5 /* timeoutSecond */,
() -> !wm.shouldShowSystemDecors(untrustedDisplay.mId));
fail("Should not allow setting system decoration flag to the untrusted virtual "
+ "display");
} catch (SecurityException e) {
// Expected security exception.
} finally {
getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
}
// Verify setting show IME flag to an untrusted display.
getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
try {
wm.setShouldShowIme(untrustedDisplay.mId, true);
// Unexpected result, restore flag to avoid affecting other tests.
wm.setShouldShowIme(untrustedDisplay.mId, false);
TestUtils.waitUntil("Waiting for show IME flag to be set",
5 /* timeoutSecond */,
() -> !wm.shouldShowIme(untrustedDisplay.mId));
fail("Should not allow setting show IME flag to the untrusted virtual display");
} catch (SecurityException e) {
// Expected security exception.
} finally {
getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
}
}
}
/**
* Test getting system decoration flag and show IME flag from the untrusted display.
*/
@Test
@FlakyTest(bugId = 130284250)
public void testGettingFlagFromUntrustedDisplay() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay untrustedDisplay = virtualDisplaySession.createDisplay();
final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
// Verify getting system decoration flag from an untrusted display.
SystemUtil.runWithShellPermissionIdentity(() -> assertFalse(
"Display should not support showing system decorations",
wm.shouldShowSystemDecors(untrustedDisplay.mId)));
// Verify getting show IME flag from an untrusted display.
SystemUtil.runWithShellPermissionIdentity(() -> assertFalse(
"Display should not support showing IME window",
wm.shouldShowIme(untrustedDisplay.mId)));
}
}
/**
* Test setting system decoration flag and show IME flag to the trusted display.
*/
@Test
@FlakyTest(bugId = 130284250)
public void testSettingFlagToTrustedDisplay() throws Exception {
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
final ActivityDisplay trustedDisplay = virtualDisplaySession
.setSimulateDisplay(true).createDisplay();
final WindowManager wm = mTargetContext.getSystemService(WindowManager.class);
// Verify setting system decoration flag to a trusted display.
SystemUtil.runWithShellPermissionIdentity(() -> {
// Assume the display should not support system decorations by default.
assertFalse(wm.shouldShowSystemDecors(trustedDisplay.mId));
try {
wm.setShouldShowSystemDecors(trustedDisplay.mId, true);
TestUtils.waitUntil("Waiting for system decoration flag to be set",
5 /* timeoutSecond */,
() -> wm.shouldShowSystemDecors(trustedDisplay.mId));
assertTrue(wm.shouldShowSystemDecors(trustedDisplay.mId));
} finally {
// Restore flag to avoid affecting other tests.
wm.setShouldShowSystemDecors(trustedDisplay.mId, false);
TestUtils.waitUntil("Waiting for system decoration flag to be set",
5 /* timeoutSecond */,
() -> !wm.shouldShowSystemDecors(trustedDisplay.mId));
}
});
// Verify setting show IME flag to a trusted display.
SystemUtil.runWithShellPermissionIdentity(() -> {
// Assume the display should not show IME window by default.
assertFalse(wm.shouldShowIme(trustedDisplay.mId));
try {
wm.setShouldShowIme(trustedDisplay.mId, true);
TestUtils.waitUntil("Waiting for show IME flag to be set",
5 /* timeoutSecond */,
() -> wm.shouldShowIme(trustedDisplay.mId));
assertTrue(wm.shouldShowIme(trustedDisplay.mId));
} finally {
// Restore flag to avoid affecting other tests.
wm.setShouldShowIme(trustedDisplay.mId, false);
TestUtils.waitUntil("Waiting for show IME flag to be set",
5 /* timeoutSecond */,
() -> !wm.shouldShowIme(trustedDisplay.mId));
}
});
}
}
}