Loading core/java/android/companion/virtual/flags/flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -83,6 +83,13 @@ flag { is_fixed_read_only: true } 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" Loading core/java/android/content/pm/ActivityInfo.java +1 −1 Original line number Diff line number Diff line Loading @@ -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 */ Loading core/res/res/values/attrs_manifest.xml +2 −2 Original line number Diff line number Diff line Loading @@ -3296,8 +3296,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" /> Loading services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java +32 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -80,6 +84,9 @@ class DisplayWindowPolicyControllerHelper { if (hasDisplayCategory(activities.get(i))) { return false; } if (!launchAllowedByDisplayPolicy(activities.get(i))) { return false; } } return true; } Loading @@ -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); Loading @@ -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) */ Loading services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +86 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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( Loading Loading
core/java/android/companion/virtual/flags/flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -83,6 +83,13 @@ flag { is_fixed_read_only: true } 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" Loading
core/java/android/content/pm/ActivityInfo.java +1 −1 Original line number Diff line number Diff line Loading @@ -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 */ Loading
core/res/res/values/attrs_manifest.xml +2 −2 Original line number Diff line number Diff line Loading @@ -3296,8 +3296,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" /> Loading
services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java +32 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -80,6 +84,9 @@ class DisplayWindowPolicyControllerHelper { if (hasDisplayCategory(activities.get(i))) { return false; } if (!launchAllowedByDisplayPolicy(activities.get(i))) { return false; } } return true; } Loading @@ -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); Loading @@ -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) */ Loading
services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +86 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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( Loading