Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bed4fc65 authored by Vladimir Komsiyski's avatar Vladimir Komsiyski
Browse files

Make canDisplayOnRemoteDevices applicable on all virtual displays.

Unless the display is a virtual one, the activity shouldn't launch

Test: presubmit
Fix: 338973239
Flag: android.companion.virtualdevice.flags.enforce_remote_device_opt_out_on_all_virtual_displays

Change-Id: I23dbfd7272cd755237e857fd6e936225ad976585
parent 33246862
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -76,6 +76,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"
+1 −1
Original line number Diff line number Diff line
@@ -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
     */
+2 −2
Original line number Diff line number Diff line
@@ -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" />
+32 −1
Original line number Diff line number Diff line
@@ -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)
     */
+86 −0
Original line number Diff line number Diff line
@@ -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(