diff options
5 files changed, 128 insertions, 4 deletions
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index b63e2cfa7fdf..6239cbea8b54 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -77,6 +77,13 @@ flag { } flag { + namespace: "virtual_devices" + name: "enforce_remote_device_opt_out_on_all_virtual_displays" + description: "Respect canDisplayOnRemoteDevices on all virtual displays" + bug: "338973239" +} + +flag { namespace: "virtual_devices" name: "virtual_display_multi_window_mode_support" description: "Add support for WINDOWING_MODE_MULTI_WINDOW to virtual displays by default" diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 57ffed47bbe3..752942970cc6 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -617,7 +617,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { */ public static final int FLAG_ENABLE_VR_MODE = 0x8000; /** - * Bit in {@link #flags} indicating if the activity can be displayed on a remote device. + * Bit in {@link #flags} indicating if the activity can be displayed on a virtual display. * Corresponds to {@link android.R.attr#canDisplayOnRemoteDevices} * @hide */ diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index f94c8ab0350e..07dadf5494c8 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -3283,8 +3283,8 @@ usually TVs. <p>Requires permission {@code android.permission.DISABLE_SYSTEM_SOUND_EFFECTS}. --> <attr name="playHomeTransitionSound" format="boolean"/> - <!-- Indicates whether the activity can be displayed on a remote device which may or - may not be running Android. --> + <!-- Indicates whether the activity can be displayed on a display that may belong to a + remote device which may or may not be running Android. --> <attr name="canDisplayOnRemoteDevices" format="boolean"/> <attr name="allowUntrustedActivityEmbedding" /> <attr name="knownActivityEmbeddingCerts" /> diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java index e0d69b063573..4ec318bee726 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java +++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java @@ -16,9 +16,12 @@ package com.android.server.wm; +import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WindowConfiguration; +import android.companion.virtualdevice.flags.Flags; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -26,6 +29,7 @@ import android.os.Process; import android.os.UserHandle; import android.util.ArraySet; import android.util.Slog; +import android.view.Display; import android.window.DisplayWindowPolicyController; import java.io.PrintWriter; @@ -80,6 +84,9 @@ class DisplayWindowPolicyControllerHelper { if (hasDisplayCategory(activities.get(i))) { return false; } + if (!launchAllowedByDisplayPolicy(activities.get(i))) { + return false; + } } return true; } @@ -95,7 +102,13 @@ class DisplayWindowPolicyControllerHelper { if (mDisplayWindowPolicyController == null) { // Missing controller means that this display has no categories for activity launch // restriction. - return !hasDisplayCategory(activityInfo); + if (hasDisplayCategory(activityInfo)) { + return false; + } + if (!launchAllowedByDisplayPolicy(activityInfo)) { + return false; + } + return true; } return mDisplayWindowPolicyController.canActivityBeLaunched(activityInfo, intent, windowingMode, launchingFromDisplayId, isNewTask); @@ -112,6 +125,24 @@ class DisplayWindowPolicyControllerHelper { return false; } + private boolean launchAllowedByDisplayPolicy(ActivityInfo aInfo) { + if (!Flags.enforceRemoteDeviceOptOutOnAllVirtualDisplays()) { + return true; + } + int displayType = mDisplayContent.getDisplay().getType(); + if (displayType != Display.TYPE_VIRTUAL && displayType != Display.TYPE_WIFI) { + return true; + } + if ((aInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) { + Slog.d(TAG, + String.format("Checking activity launch on display %d, activity requires" + + " android:canDisplayOnRemoteDevices=true", + mDisplayContent.mDisplayId)); + return false; + } + return true; + } + /** * @see DisplayWindowPolicyController#keepActivityOnWindowFlagsChanged(ActivityInfo, int, int) */ diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index ff1c6c8fc70c..d0080d29f82b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -102,6 +102,7 @@ import android.provider.DeviceConfig; import android.service.voice.IVoiceInteractionSession; import android.util.Pair; import android.util.Size; +import android.view.Display; import android.view.Gravity; import android.view.RemoteAnimationAdapter; import android.window.TaskFragmentOrganizerToken; @@ -941,6 +942,91 @@ public class ActivityStarterTests extends WindowTestsBase { notNull() /* options */); } + + /** + * This test ensures that activity launch on a secondary display is allowed if the activity did + * not opt out from showing on remote devices. + */ + @Test + public void testStartActivityOnVirtualDisplay() { + final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK, + false /* mockGetRootTask */); + starter.mRequest.activityInfo.flags |= ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES; + + // Create a virtual display at bottom. + final TestDisplayContent secondaryDisplay = + new TestDisplayContent.Builder(mAtm, 1000, 1500) + .setType(Display.TYPE_VIRTUAL) + .setPosition(POSITION_BOTTOM).build(); + final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea(); + final Task stack = secondaryTaskContainer.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + // Create an activity record on the top of secondary display. + final ActivityRecord topActivityOnSecondaryDisplay = createSingleTaskActivityOn(stack); + + // Put an activity on default display as the top focused activity. + new ActivityBuilder(mAtm).setCreateTask(true).build(); + + // Start activity with the same intent as {@code topActivityOnSecondaryDisplay} + // on secondary display. + final ActivityOptions options = ActivityOptions.makeBasic() + .setLaunchDisplayId(secondaryDisplay.mDisplayId); + final int result = starter.setReason("testStartActivityOnVirtualDisplay") + .setIntent(topActivityOnSecondaryDisplay.intent) + .setActivityOptions(options.toBundle()) + .execute(); + + // Ensure result is delivering intent to top. + assertEquals(START_DELIVERED_TO_TOP, result); + + // Ensure secondary display only creates one stack. + verify(secondaryTaskContainer, times(1)).createRootTask(anyInt(), anyInt(), anyBoolean()); + } + + /** + * This test ensures that activity launch on a secondary display is disallowed if the activity + * opted out from showing on remote devices. + */ + @EnableFlags(android.companion.virtualdevice.flags.Flags + .FLAG_ENFORCE_REMOTE_DEVICE_OPT_OUT_ON_ALL_VIRTUAL_DISPLAYS) + @Test + public void testStartOptedOutActivityOnVirtualDisplay() { + final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK, + false /* mockGetRootTask */); + starter.mRequest.activityInfo.flags &= ~ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES; + + // Create a virtual display at bottom. + final TestDisplayContent secondaryDisplay = + new TestDisplayContent.Builder(mAtm, 1000, 1500) + .setType(Display.TYPE_VIRTUAL) + .setPosition(POSITION_BOTTOM).build(); + final TaskDisplayArea secondaryTaskContainer = secondaryDisplay.getDefaultTaskDisplayArea(); + final Task stack = secondaryTaskContainer.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + // Create an activity record on the top of secondary display. + final ActivityRecord topActivityOnSecondaryDisplay = createSingleTaskActivityOn(stack); + + // Put an activity on default display as the top focused activity. + new ActivityBuilder(mAtm).setCreateTask(true).build(); + + // Start activity with the same intent as {@code topActivityOnSecondaryDisplay} + // on secondary display. + final ActivityOptions options = ActivityOptions.makeBasic() + .setLaunchDisplayId(secondaryDisplay.mDisplayId); + final int result = starter.setReason("testStartOptedOutActivityOnVirtualDisplay") + .setIntent(topActivityOnSecondaryDisplay.intent) + .setActivityOptions(options.toBundle()) + .execute(); + + // Ensure result is canceled. + assertEquals(START_CANCELED, result); + + // Ensure secondary display only creates one stack. + verify(secondaryTaskContainer, times(1)).createRootTask(anyInt(), anyInt(), anyBoolean()); + } + @Test public void testWasVisibleInRestartAttempt() { final ActivityStarter starter = prepareStarter( |