| /* |
| * 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.WindowManagerState.STATE_RESUMED; |
| import static android.server.wm.ComponentNameUtils.getActivityName; |
| import static android.server.wm.MockImeHelper.createManagedMockImeSession; |
| import static android.server.wm.MultiDisplaySystemDecorationTests.ImeTestActivity; |
| 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 android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; |
| import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE; |
| import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; |
| |
| import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; |
| |
| import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher; |
| import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotEquals; |
| 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.graphics.Rect; |
| import android.os.Bundle; |
| import android.platform.test.annotations.Presubmit; |
| import android.server.wm.WindowManagerState.DisplayContent; |
| import android.server.wm.WindowManagerState.ActivityTask; |
| import android.server.wm.CommandSession.ActivitySession; |
| import android.server.wm.TestJournalProvider.TestJournalContainer; |
| import android.view.Display; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.WindowManager; |
| import android.widget.EditText; |
| |
| import com.android.compatibility.common.util.SystemUtil; |
| import com.android.compatibility.common.util.TestUtils; |
| import com.android.cts.mockime.ImeEventStream; |
| import com.android.cts.mockime.MockImeSession; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Build/Install/Run: |
| * atest CtsWindowManagerDeviceTestCases:MultiDisplaySecurityTests |
| * |
| * Tests if be allowed to launch an activity on multi-display environment. |
| */ |
| @Presubmit |
| @android.server.wm.annotation.Group3 |
| 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 not be |
| * allowed. |
| */ |
| @Test |
| public void testLaunchWithoutPermissionOnVirtualDisplayByOwner() { |
| // Create new virtual display. |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay(); |
| |
| separateTestJournal(); |
| |
| // Try to launch an activity and check if security exception was triggered. |
| getLaunchActivityBuilder() |
| .setUseBroadcastReceiver(LAUNCH_BROADCAST_RECEIVER, LAUNCH_BROADCAST_ACTION) |
| .setDisplayId(newDisplay.mId) |
| .setTargetActivity(TEST_ACTIVITY) |
| .execute(); |
| assertSecurityExceptionFromActivityLauncher(); |
| mWmState.computeState(TEST_ACTIVITY); |
| assertFalse("Restricted activity must not be launched", |
| mWmState.containsActivity(TEST_ACTIVITY)); |
| } |
| |
| /** |
| * Tests launching an activity on a virtual display without special permission must not be |
| * allowed. |
| */ |
| @Test |
| public void testLaunchWithoutPermissionOnVirtualDisplay() { |
| // Create new virtual display. |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession().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(); |
| mWmState.computeState(TEST_ACTIVITY); |
| assertFalse("Restricted activity must not be launched", |
| mWmState.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() { |
| // Create new virtual display. |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay(); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); |
| |
| waitAndAssertActivityStateOnDisplay(LAUNCHING_ACTIVITY, STATE_RESUMED, 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(); |
| } |
| |
| private boolean isActivityStartAllowedOnDisplay(int displayId, ComponentName activity) { |
| final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(activity); |
| return mTargetContext.getSystemService(ActivityManager.class) |
| .isActivityStartAllowedOnDisplay(mTargetContext, displayId, intent); |
| } |
| |
| /** |
| * 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() { |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .setSimulateDisplay(true) |
| .createDisplay(); |
| |
| assertTrue(isActivityStartAllowedOnDisplay(newDisplay.mId, TEST_ACTIVITY)); |
| } |
| |
| /** |
| * 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() { |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .setPublicDisplay(true) |
| .createDisplay(); |
| |
| SystemUtil.runWithShellPermissionIdentity( |
| () -> assertTrue(isActivityStartAllowedOnDisplay( |
| newDisplay.mId, SECOND_NO_EMBEDDING_ACTIVITY)), |
| "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() { |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .setPublicDisplay(false) |
| .createDisplay(); |
| |
| SystemUtil.runWithShellPermissionIdentity( |
| () -> assertTrue(isActivityStartAllowedOnDisplay( |
| newDisplay.mId, SECOND_NO_EMBEDDING_ACTIVITY)), |
| "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() { |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .setPublicDisplay(true) |
| .createDisplay(); |
| |
| assertFalse(isActivityStartAllowedOnDisplay(newDisplay.mId, SECOND_ACTIVITY)); |
| } |
| |
| /** |
| * 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() { |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .setPublicDisplay(true) |
| .createDisplay(); |
| |
| SystemUtil.runWithShellPermissionIdentity( |
| () -> assertFalse(isActivityStartAllowedOnDisplay( |
| newDisplay.mId, SECOND_NO_EMBEDDING_ACTIVITY)), |
| "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() { |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .setPublicDisplay(true) |
| .createDisplay(); |
| |
| SystemUtil.runWithShellPermissionIdentity( |
| () -> assertTrue(isActivityStartAllowedOnDisplay( |
| newDisplay.mId, SECOND_ACTIVITY)), |
| "android.permission.ACTIVITY_EMBEDDING"); |
| } |
| |
| /** |
| * Tests |
| * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)} |
| * for a private virtual display. |
| */ |
| @Test |
| public void testCantAccessPrivateVirtualDisplay() { |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .setPublicDisplay(false) |
| .createDisplay(); |
| |
| assertFalse(isActivityStartAllowedOnDisplay(newDisplay.mId, SECOND_ACTIVITY)); |
| } |
| |
| /** |
| * 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 testCantAccessPrivateVirtualDisplayByOwner() { |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .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(false); |
| } |
| |
| /** |
| * 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() { |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .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 { |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .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; |
| final Bundle resultExtras = Condition.waitForResult( |
| new Condition<Bundle>("activity start check for " + component) |
| .setRetryIntervalMs(500) |
| .setResultSupplier(() -> TestJournalContainer.get(component).extras) |
| .setResultValidator(extras -> extras.containsKey( |
| ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY))); |
| if (resultExtras != null) { |
| assertEquals("Activity start check must match", expected, resultExtras |
| .getBoolean(ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY)); |
| return; |
| } |
| fail("Expected activity start check from " + component + " not found"); |
| } |
| |
| @Test |
| public void testDisplayHasAccess_UIDCanPresentOnPrivateDisplay() { |
| final VirtualDisplayLauncher virtualDisplayLauncher = |
| mObjectTracker.manage(new VirtualDisplayLauncher()); |
| // Create a virtual private display. |
| final DisplayContent 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() { |
| final VirtualDisplayLauncher virtualDisplayLauncher = |
| mObjectTracker.manage(new VirtualDisplayLauncher()); |
| // Create a virtual private display. |
| final DisplayContent 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() { |
| // Create a virtual private display. |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .setPublicDisplay(false) |
| .createDisplay(); |
| try { |
| final Display display = mDm.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(){ |
| // Create new virtual display. |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay(); |
| mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| mWmState.assertFocusedActivity("Virtual display activity must be on top", |
| VIRTUAL_DISPLAY_ACTIVITY); |
| final int defaultDisplayFocusedStackId = mWmState.getFocusedStackId(); |
| ActivityTask frontStack = mWmState.getRootTask( |
| defaultDisplayFocusedStackId); |
| assertEquals("Top stack must remain on primary display", |
| DEFAULT_DISPLAY, frontStack.mDisplayId); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| |
| waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId, |
| "Test activity must be on secondary display"); |
| assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY), |
| pair(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); |
| |
| waitAndAssertActivityStateOnDisplay(SECOND_ACTIVITY, STATE_RESUMED, newDisplay.mId, |
| "Second activity must be on newly launched app"); |
| assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY), |
| pair(newDisplay.mId, SECOND_ACTIVITY)); |
| } |
| |
| /** Test that launching from app that is on external display is allowed. */ |
| @Test |
| public void testPermissionLaunchFromAppOnSecondary() { |
| // Create new simulated display. |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .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() { |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .setSimulateDisplay(true) |
| .createDisplay(); |
| |
| launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); |
| mWmState.computeState(LAUNCHING_ACTIVITY); |
| |
| // Check that the first activity is launched onto the secondary display. |
| final int frontStackId = mWmState.getFrontRootTaskId(newDisplay.mId); |
| ActivityTask frontStack = mWmState.getRootTask(frontStackId); |
| assertEquals("Activity launched on secondary display must be resumed", |
| getActivityName(LAUNCHING_ACTIVITY), frontStack.mResumedActivity); |
| mWmState.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 = mWmState.getRootTask(frontStackId); |
| assertEquals("Secondary display must contain 1 task", 1, |
| mWmState.getDisplay(newDisplay.mId).getRootTasks().size()); |
| } |
| |
| /** |
| * 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() { |
| // Create new virtual display. |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay(); |
| mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| mWmState.assertFocusedActivity("Virtual display activity must be focused", |
| VIRTUAL_DISPLAY_ACTIVITY); |
| final int defaultDisplayFocusedStackId = mWmState.getFocusedStackId(); |
| ActivityTask frontStack = mWmState.getRootTask( |
| defaultDisplayFocusedStackId); |
| assertEquals("Top stack must remain on primary display", |
| DEFAULT_DISPLAY, frontStack.mDisplayId); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId, |
| "Test 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(); |
| } |
| |
| /** |
| * Test that only private virtual display can show content with insecure keyguard. |
| */ |
| @Test |
| public void testFlagShowWithInsecureKeyguardOnPublicVirtualDisplay() { |
| // Try to create new show-with-insecure-keyguard public virtual display. |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .setPublicDisplay(true) |
| .setCanShowWithInsecureKeyguard(true) |
| .createDisplay(false /* mustBeCreated */); |
| |
| // Check that the display is not created. |
| assertNull(newDisplay); |
| } |
| |
| /** |
| * Test setting system decoration flag and show IME flag without sufficient permissions. |
| */ |
| @Test |
| public void testSettingFlagWithoutInternalSystemPermission() throws Exception { |
| // The reason to use a trusted display is that we can guarantee the security exception |
| // is coming from lacking internal system permission. |
| final DisplayContent trustedDisplay = createManagedVirtualDisplaySession() |
| .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.setDisplayImePolicy(trustedDisplay.mId, DISPLAY_IME_POLICY_LOCAL); |
| |
| // Unexpected result, restore flag to avoid affecting other tests. |
| wm.setDisplayImePolicy(trustedDisplay.mId, DISPLAY_IME_POLICY_FALLBACK_DISPLAY); |
| TestUtils.waitUntil("Waiting for show IME flag to be set", |
| 5 /* timeoutSecond */, |
| () -> (wm.getDisplayImePolicy(trustedDisplay.mId) |
| == DISPLAY_IME_POLICY_FALLBACK_DISPLAY)); |
| 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 |
| public void testGettingFlagWithoutInternalSystemPermission() { |
| // The reason to use a trusted display is that we can guarantee the security exception |
| // is coming from lacking internal system permission. |
| final DisplayContent trustedDisplay = createManagedVirtualDisplaySession() |
| .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.getDisplayImePolicy(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 |
| public void testSettingFlagToUntrustedDisplay() throws Exception { |
| final DisplayContent untrustedDisplay = createManagedVirtualDisplaySession() |
| .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.setDisplayImePolicy(untrustedDisplay.mId, DISPLAY_IME_POLICY_LOCAL); |
| |
| // Unexpected result, restore flag to avoid affecting other tests. |
| wm.setDisplayImePolicy(untrustedDisplay.mId, DISPLAY_IME_POLICY_FALLBACK_DISPLAY); |
| TestUtils.waitUntil("Waiting for show IME flag to be set", |
| 5 /* timeoutSecond */, |
| () -> (wm.getDisplayImePolicy(untrustedDisplay.mId) |
| == DISPLAY_IME_POLICY_FALLBACK_DISPLAY)); |
| 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 |
| public void testGettingFlagFromUntrustedDisplay() { |
| final DisplayContent untrustedDisplay = createManagedVirtualDisplaySession() |
| .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(() -> assertEquals( |
| "Display should not support showing IME window", |
| wm.getDisplayImePolicy(untrustedDisplay.mId), |
| DISPLAY_IME_POLICY_FALLBACK_DISPLAY)); |
| } |
| |
| /** |
| * Test setting system decoration flag and show IME flag to the trusted display. |
| */ |
| @Test |
| public void testSettingFlagToTrustedDisplay() throws Exception { |
| final DisplayContent trustedDisplay = createManagedVirtualDisplaySession() |
| .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. |
| assertEquals(DISPLAY_IME_POLICY_FALLBACK_DISPLAY, |
| wm.getDisplayImePolicy(trustedDisplay.mId)); |
| |
| try { |
| wm.setDisplayImePolicy(trustedDisplay.mId, DISPLAY_IME_POLICY_LOCAL); |
| TestUtils.waitUntil("Waiting for show IME flag to be set", |
| 5 /* timeoutSecond */, |
| () -> (wm.getDisplayImePolicy(trustedDisplay.mId) |
| == DISPLAY_IME_POLICY_LOCAL)); |
| |
| assertEquals(DISPLAY_IME_POLICY_LOCAL, wm.getDisplayImePolicy(trustedDisplay.mId)); |
| |
| wm.setDisplayImePolicy(trustedDisplay.mId, DISPLAY_IME_POLICY_HIDE); |
| TestUtils.waitUntil("Waiting for show IME flag to be set", |
| 5 /* timeoutSecond */, |
| () -> (wm.getDisplayImePolicy(trustedDisplay.mId) |
| == DISPLAY_IME_POLICY_HIDE)); |
| |
| assertEquals(DISPLAY_IME_POLICY_HIDE, wm.getDisplayImePolicy(trustedDisplay.mId)); |
| } finally { |
| // Restore flag to avoid affecting other tests. |
| wm.setDisplayImePolicy(trustedDisplay.mId, DISPLAY_IME_POLICY_FALLBACK_DISPLAY); |
| TestUtils.waitUntil("Waiting for show IME flag to be set", |
| 5 /* timeoutSecond */, |
| () -> (wm.getDisplayImePolicy(trustedDisplay.mId) |
| == DISPLAY_IME_POLICY_FALLBACK_DISPLAY)); |
| } |
| }); |
| } |
| |
| @Test |
| public void testNoInputConnectionForUntrustedVirtualDisplay() throws Exception { |
| assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); |
| |
| final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2); |
| |
| final MockImeSession mockImeSession = createManagedMockImeSession(this); |
| final TestActivitySession<ImeTestActivity> imeTestActivitySession = |
| createManagedTestActivitySession(); |
| // Create a untrusted virtual display and assume the display should not show IME window. |
| final DisplayContent newDisplay = createManagedVirtualDisplaySession() |
| .setPublicDisplay(true).createDisplay(); |
| |
| // Launch Ime test activity in virtual display. |
| imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class, |
| newDisplay.mId); |
| // Verify that activity which lives in untrusted display should not be focused. |
| assertNotEquals("ImeTestActivity should not be focused", |
| mWmState.getFocusedActivity(), |
| imeTestActivitySession.getActivity().getComponentName().toString()); |
| |
| // Expect onStartInput won't executed in the IME client. |
| final ImeEventStream stream = mockImeSession.openEventStream(); |
| final EditText editText = imeTestActivitySession.getActivity().mEditText; |
| imeTestActivitySession.runOnMainSyncAndWait( |
| imeTestActivitySession.getActivity()::showSoftInput); |
| notExpectEvent(stream, editorMatcher("onStartInput", |
| editText.getPrivateImeOptions()), NOT_EXPECT_TIMEOUT); |
| |
| // Expect onStartInput / showSoftInput would be executed when user tapping on the |
| // untrusted display intentionally. |
| final int[] location = new int[2]; |
| editText.getLocationOnScreen(location); |
| tapOnDisplaySync(location[0], location[1], newDisplay.mId); |
| imeTestActivitySession.runOnMainSyncAndWait( |
| imeTestActivitySession.getActivity()::showSoftInput); |
| waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY, |
| editorMatcher("onStartInput", editText.getPrivateImeOptions()), |
| event -> "showSoftInput".equals(event.getEventName())); |
| |
| // Switch focus to top focused display as default display, verify onStartInput won't |
| // be called since the untrusted display should no longer get focus. |
| tapOnDisplayCenter(DEFAULT_DISPLAY); |
| mWmState.computeState(); |
| assertEquals(DEFAULT_DISPLAY, mWmState.getFocusedDisplayId()); |
| imeTestActivitySession.getActivity().resetPrivateImeOptionsIdentifier(); |
| imeTestActivitySession.runOnMainSyncAndWait( |
| imeTestActivitySession.getActivity()::showSoftInput); |
| notExpectEvent(stream, editorMatcher("onStartInput", |
| editText.getPrivateImeOptions()), NOT_EXPECT_TIMEOUT); |
| } |
| } |