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

Commit b7aa9adf authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implement ComputerControl Activity policy" into main

parents dde2691e 7c507123
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -256,3 +256,23 @@ flag {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "computer_control_activity_policy_strict"
    namespace: "virtual_devices"
    description: "Implements a strict Activity policy for ComputerControl VirtualDevices"
    bug: "437849470"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "computer_control_activity_policy_relaxed"
    namespace: "virtual_devices"
    description: "Implements a relaxed Activity policy for ComputerControl VirtualDevices"
    bug: "437849470"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+47 −6
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.companion.virtual.computercontrol;

import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;

import android.annotation.IntRange;
@@ -32,11 +33,13 @@ import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.computercontrol.ComputerControlSessionParams;
import android.companion.virtual.computercontrol.IComputerControlSession;
import android.companion.virtual.computercontrol.IInteractiveMirrorDisplay;
import android.companion.virtualdevice.flags.Flags;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
@@ -62,6 +65,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.wm.WindowManagerInternal;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -115,11 +120,6 @@ final class ComputerControlSessionImpl extends IComputerControlSession.Stub
                .setName(mParams.getName())
                .setDevicePolicy(POLICY_TYPE_RECENTS, DEVICE_POLICY_CUSTOM)
                .build();
        final String permissionControllerPackage = mInjector.getPermissionControllerPackageName();
        final ActivityPolicyExemption permissionController =
                new ActivityPolicyExemption.Builder()
                        .setPackageName(permissionControllerPackage)
                        .build();

        int displayFlags = DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
                | DisplayManager.VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED;
@@ -164,7 +164,8 @@ final class ComputerControlSessionImpl extends IComputerControlSession.Stub
        try {
            mVirtualDevice = virtualDeviceFactory.createVirtualDevice(mAppToken, attributionSource,
                    virtualDeviceParams, new ComputerControlActivityListener());
            mVirtualDevice.addActivityPolicyExemption(permissionController);

            applyActivityPolicy();

            // Create the display with a clean identity so it can be trusted.
            mVirtualDisplayId = Binder.withCleanCallingIdentity(() -> {
@@ -210,6 +211,32 @@ final class ComputerControlSessionImpl extends IComputerControlSession.Stub
        }
    }

    private void applyActivityPolicy() throws RemoteException {
        String permissionControllerPackage = mInjector.getPermissionControllerPackageName();

        List<String> exemptedPackageNames = new ArrayList<>();
        if (Flags.computerControlActivityPolicyStrict()) {
            mVirtualDevice.setDevicePolicy(POLICY_TYPE_ACTIVITY, DEVICE_POLICY_CUSTOM);

            exemptedPackageNames.addAll(mParams.getTargetPackageNames());
            exemptedPackageNames.remove(permissionControllerPackage);
        } else if (Flags.computerControlActivityPolicyRelaxed()) {
            mVirtualDevice.setDevicePolicy(POLICY_TYPE_ACTIVITY, DEVICE_POLICY_CUSTOM);

            exemptedPackageNames.addAll(mParams.getTargetPackageNames());
            exemptedPackageNames.addAll(mInjector.getAllApplicationsWithoutLauncherActivity());
            exemptedPackageNames.remove(permissionControllerPackage);
        } else {
            exemptedPackageNames.add(permissionControllerPackage);
        }
        for (String allowedPackageName : exemptedPackageNames) {
            mVirtualDevice.addActivityPolicyExemption(
                    new ActivityPolicyExemption.Builder()
                            .setPackageName(allowedPackageName)
                            .build());
        }
    }

    @Override
    public int getVirtualDisplayId() {
        return mVirtualDisplayId;
@@ -380,6 +407,20 @@ final class ComputerControlSessionImpl extends IComputerControlSession.Stub
            return mPackageManager.getPermissionControllerPackageName();
        }

        public List<String> getAllApplicationsWithoutLauncherActivity() {
            List<String> result = new ArrayList<>();
            List<ApplicationInfo> installedApplications =
                    mPackageManager.getInstalledApplications(0);
            for (int i = 0; i < installedApplications.size(); i++) {
                ApplicationInfo applicationInfo = installedApplications.get(i);
                if (mPackageManager.getLaunchIntentForPackage(applicationInfo.packageName)
                        == null) {
                    result.add(applicationInfo.packageName);
                }
            }
            return result;
        }

        public void launchApplicationOnDisplayAsUser(String packageName, int displayId,
                UserHandle user) {
            Intent intent = mPackageManager.getLaunchIntentForPackage(packageName);
+152 −17
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.companion.virtual.computercontrol;

import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;

import static com.google.common.truth.Truth.assertThat;
@@ -26,6 +27,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -35,6 +37,7 @@ import android.companion.virtual.ActivityPolicyExemption;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.computercontrol.ComputerControlSessionParams;
import android.companion.virtualdevice.flags.Flags;
import android.content.AttributionSource;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplayConfig;
@@ -45,7 +48,10 @@ import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchscreenConfig;
import android.os.Binder;
import android.os.IBinder;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.DisplayInfo;
import android.view.WindowManager;

@@ -53,6 +59,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -61,11 +68,14 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.List;

@Presubmit
@RunWith(AndroidJUnit4.class)
public class ComputerControlSessionTest {
    @Rule
    public SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    private static final String PERMISSION_CONTROLLER_PACKAGE = "permission.controller.package";

@@ -75,8 +85,11 @@ public class ComputerControlSessionTest {
    private static final int DISPLAY_DPI = 480;
    private static final String TARGET_PACKAGE_1 = "com.android.foo";
    private static final String TARGET_PACKAGE_2 = "com.android.bar";
    private static final String TARGET_PACKAGE_3 = "com.android.foobar";
    private static final List<String> TARGET_PACKAGE_NAMES =
            List.of(TARGET_PACKAGE_1, TARGET_PACKAGE_2);
    private static final List<String> PACKAGES_WITHOUT_LAUNCHER_ACTIVITY = List.of(
            TARGET_PACKAGE_3);
    private static final String UNDECLARED_TARGET_PACKAGE = "com.android.baz";

    @Mock
@@ -104,7 +117,8 @@ public class ComputerControlSessionTest {

    private AutoCloseable mMockitoSession;
    private final IBinder mAppToken = new Binder();
    private final ComputerControlSessionParams mParams = new ComputerControlSessionParams.Builder()
    private final ComputerControlSessionParams mDefaultParams =
            new ComputerControlSessionParams.Builder()
                    .setName(ComputerControlSessionTest.class.getSimpleName())
                    .setTargetPackageNames(TARGET_PACKAGE_NAMES)
                    .build();
@@ -122,13 +136,12 @@ public class ComputerControlSessionTest {

        when(mInjector.getPermissionControllerPackageName())
                .thenReturn(PERMISSION_CONTROLLER_PACKAGE);
        when(mInjector.getAllApplicationsWithoutLauncherActivity())
                .thenReturn(PACKAGES_WITHOUT_LAUNCHER_ACTIVITY);
        when(mVirtualDeviceFactory.createVirtualDevice(any(), any(), any(), any()))
                .thenReturn(mVirtualDevice);
        when(mVirtualDevice.createVirtualDisplay(any(), any())).thenReturn(VIRTUAL_DISPLAY_ID);
        when(mVirtualDevice.createVirtualTouchscreen(any(), any())).thenReturn(mVirtualTouchscreen);
        mSession = new ComputerControlSessionImpl(mAppToken, mParams,
                AttributionSource.myAttributionSource(), mVirtualDeviceFactory, mOnClosedListener,
                mInjector);
    }

    @After
@@ -138,23 +151,20 @@ public class ComputerControlSessionTest {

    @Test
    public void createSessionWithoutDisplaySurface_appliesCorrectParams() throws Exception {
        createComputerControlSession(mDefaultParams);

        verify(mVirtualDeviceFactory).createVirtualDevice(
                eq(mAppToken), any(), mVirtualDeviceParamsArgumentCaptor.capture(), any());
        assertThat(mVirtualDeviceParamsArgumentCaptor.getValue().getName())
                .isEqualTo(mParams.getName());
                .isEqualTo(mDefaultParams.getName());
        assertThat(mVirtualDeviceParamsArgumentCaptor.getValue()
                .getDevicePolicy(POLICY_TYPE_RECENTS))
                .isEqualTo(DEVICE_POLICY_CUSTOM);

        verify(mVirtualDevice).addActivityPolicyExemption(
                mActivityPolicyExemptionArgumentCaptor.capture());
        assertThat(mActivityPolicyExemptionArgumentCaptor.getValue().getPackageName())
                .isEqualTo(PERMISSION_CONTROLLER_PACKAGE);

        verify(mVirtualDevice).createVirtualDisplay(
                mVirtualDisplayConfigArgumentCaptor.capture(), any());
        VirtualDisplayConfig virtualDisplayConfig = mVirtualDisplayConfigArgumentCaptor.getValue();
        assertThat(virtualDisplayConfig.getName()).contains(mParams.getName());
        assertThat(virtualDisplayConfig.getName()).contains(mDefaultParams.getName());

        assertThat(virtualDisplayConfig.getDensityDpi()).isEqualTo(DISPLAY_DPI);
        assertThat(virtualDisplayConfig.getHeight()).isEqualTo(DISPLAY_HEIGHT);
@@ -173,14 +183,14 @@ public class ComputerControlSessionTest {
                mVirtualDpadConfigArgumentCaptor.capture(), any());
        VirtualDpadConfig virtualDpadConfig = mVirtualDpadConfigArgumentCaptor.getValue();
        assertThat(virtualDpadConfig.getAssociatedDisplayId()).isEqualTo(VIRTUAL_DISPLAY_ID);
        assertThat(virtualDpadConfig.getInputDeviceName()).contains(mParams.getName());
        assertThat(virtualDpadConfig.getInputDeviceName()).contains(mDefaultParams.getName());

        verify(mVirtualDevice).createVirtualKeyboard(
                mVirtualKeyboardConfigArgumentCaptor.capture(), any());
        VirtualKeyboardConfig virtualKeyboardConfig =
                mVirtualKeyboardConfigArgumentCaptor.getValue();
        assertThat(virtualKeyboardConfig.getAssociatedDisplayId()).isEqualTo(VIRTUAL_DISPLAY_ID);
        assertThat(virtualKeyboardConfig.getInputDeviceName()).contains(mParams.getName());
        assertThat(virtualKeyboardConfig.getInputDeviceName()).contains(mDefaultParams.getName());

        verify(mVirtualDevice).createVirtualTouchscreen(
                mVirtualTouchscreenConfigArgumentCaptor.capture(), any());
@@ -189,11 +199,109 @@ public class ComputerControlSessionTest {
        assertThat(virtualTouchscreenConfig.getAssociatedDisplayId()).isEqualTo(VIRTUAL_DISPLAY_ID);
        assertThat(virtualTouchscreenConfig.getWidth()).isEqualTo(DISPLAY_WIDTH);
        assertThat(virtualTouchscreenConfig.getHeight()).isEqualTo(DISPLAY_HEIGHT);
        assertThat(virtualTouchscreenConfig.getInputDeviceName()).contains(mParams.getName());
        assertThat(virtualTouchscreenConfig.getInputDeviceName()).contains(
                mDefaultParams.getName());
    }

    @Test
    @DisableFlags(value = {Flags.FLAG_COMPUTER_CONTROL_ACTIVITY_POLICY_RELAXED,
            Flags.FLAG_COMPUTER_CONTROL_ACTIVITY_POLICY_STRICT})
    public void createSession_noActivityPolicy() throws Exception {
        createComputerControlSession(mDefaultParams);
        verify(mVirtualDevice, never()).setDevicePolicy(eq(POLICY_TYPE_ACTIVITY), anyInt());

        verify(mVirtualDevice).addActivityPolicyExemption(
                argThat(new MatchesActivityPolicyExcemption(PERMISSION_CONTROLLER_PACKAGE)));
    }

    @Test
    @EnableFlags(Flags.FLAG_COMPUTER_CONTROL_ACTIVITY_POLICY_STRICT)
    @DisableFlags(Flags.FLAG_COMPUTER_CONTROL_ACTIVITY_POLICY_RELAXED)
    public void createSession_strictActivityPolicy() throws Exception {
        createComputerControlSession(mDefaultParams);

        verify(mVirtualDevice).setDevicePolicy(POLICY_TYPE_ACTIVITY, DEVICE_POLICY_CUSTOM);

        for (String expected : TARGET_PACKAGE_NAMES) {
            verify(mVirtualDevice).addActivityPolicyExemption(
                    argThat(new MatchesActivityPolicyExcemption(expected)));
        }
    }

    @Test
    @EnableFlags(Flags.FLAG_COMPUTER_CONTROL_ACTIVITY_POLICY_STRICT)
    @DisableFlags(Flags.FLAG_COMPUTER_CONTROL_ACTIVITY_POLICY_RELAXED)
    public void createSession_strictActivityPolicy_removesPermissionController() throws Exception {
        List<String> targetPackageNames = List.of(TARGET_PACKAGE_1, PERMISSION_CONTROLLER_PACKAGE);
        createComputerControlSession(new ComputerControlSessionParams.Builder()
                .setTargetPackageNames(targetPackageNames)
                .setName(ComputerControlSessionTest.class.getSimpleName())
                .build());

        verify(mVirtualDevice).setDevicePolicy(POLICY_TYPE_ACTIVITY, DEVICE_POLICY_CUSTOM);

        verify(mVirtualDevice).addActivityPolicyExemption(
                argThat(new MatchesActivityPolicyExcemption(TARGET_PACKAGE_1)));
        verify(mVirtualDevice, never()).addActivityPolicyExemption(
                argThat(new MatchesActivityPolicyExcemption(PERMISSION_CONTROLLER_PACKAGE)));
    }

    @Test
    @EnableFlags(Flags.FLAG_COMPUTER_CONTROL_ACTIVITY_POLICY_RELAXED)
    @DisableFlags(Flags.FLAG_COMPUTER_CONTROL_ACTIVITY_POLICY_STRICT)
    public void createSession_relaxedActivityPolicy() throws Exception {
        createComputerControlSession(mDefaultParams);

        verify(mVirtualDevice).setDevicePolicy(POLICY_TYPE_ACTIVITY, DEVICE_POLICY_CUSTOM);

        List<String> targetPackageNames = new ArrayList<>(mDefaultParams.getTargetPackageNames());
        targetPackageNames.addAll(PACKAGES_WITHOUT_LAUNCHER_ACTIVITY);

        for (String expected : targetPackageNames) {
            verify(mVirtualDevice).addActivityPolicyExemption(
                    argThat(new MatchesActivityPolicyExcemption(expected)));
        }
    }

    @Test
    @EnableFlags(Flags.FLAG_COMPUTER_CONTROL_ACTIVITY_POLICY_RELAXED)
    @DisableFlags(Flags.FLAG_COMPUTER_CONTROL_ACTIVITY_POLICY_STRICT)
    public void createSession_relaxedActivityPolicy_removesPermissionController() throws Exception {
        createComputerControlSession(new ComputerControlSessionParams.Builder()
                .setName(ComputerControlSessionTest.class.getSimpleName())
                .setTargetPackageNames(List.of(TARGET_PACKAGE_1, PERMISSION_CONTROLLER_PACKAGE))
                .build());

        verify(mVirtualDevice).setDevicePolicy(POLICY_TYPE_ACTIVITY, DEVICE_POLICY_CUSTOM);

        List<String> targetPackageNames = new ArrayList<>();
        targetPackageNames.add(TARGET_PACKAGE_1);
        targetPackageNames.addAll(PACKAGES_WITHOUT_LAUNCHER_ACTIVITY);
        for (String expected : targetPackageNames) {
            verify(mVirtualDevice).addActivityPolicyExemption(
                    argThat(new MatchesActivityPolicyExcemption(expected)));
        }
        verify(mVirtualDevice, never()).addActivityPolicyExemption(
                argThat(new MatchesActivityPolicyExcemption(PERMISSION_CONTROLLER_PACKAGE)));
    }

    @Test
    @EnableFlags(value = {Flags.FLAG_COMPUTER_CONTROL_ACTIVITY_POLICY_RELAXED,
            Flags.FLAG_COMPUTER_CONTROL_ACTIVITY_POLICY_STRICT})
    public void createSession_bothActivityPolicies() throws Exception {
        createComputerControlSession(mDefaultParams);

        verify(mVirtualDevice).setDevicePolicy(POLICY_TYPE_ACTIVITY, DEVICE_POLICY_CUSTOM);

        for (String expected : TARGET_PACKAGE_NAMES) {
            verify(mVirtualDevice).addActivityPolicyExemption(
                    argThat(new MatchesActivityPolicyExcemption(expected)));
        }
    }

    @Test
    public void closeSession_closesVirtualDevice() throws Exception {
        createComputerControlSession(mDefaultParams);
        mSession.close();
        verify(mVirtualDevice).setDevicePolicy(POLICY_TYPE_RECENTS, DEVICE_POLICY_DEFAULT);
        verify(mVirtualDevice).close();
@@ -202,16 +310,19 @@ public class ComputerControlSessionTest {

    @Test
    public void getVirtualDisplayId_returnsCreatedDisplay() {
        createComputerControlSession(mDefaultParams);
        assertThat(mSession.getVirtualDisplayId()).isEqualTo(VIRTUAL_DISPLAY_ID);
    }

    @Test
    public void createSession_disablesAnimationsOnDisplay() {
        createComputerControlSession(mDefaultParams);
        verify(mInjector).disableAnimationsForDisplay(VIRTUAL_DISPLAY_ID);
    }

    @Test
    public void launchApplication_launchesApplication() {
        createComputerControlSession(mDefaultParams);
        mSession.launchApplication(TARGET_PACKAGE_1);
        verify(mInjector).launchApplicationOnDisplayAsUser(
                eq(TARGET_PACKAGE_1), eq(VIRTUAL_DISPLAY_ID), any());
@@ -219,12 +330,14 @@ public class ComputerControlSessionTest {

    @Test
    public void launchApplication_undeclaredPackage_throws() {
        createComputerControlSession(mDefaultParams);
        assertThrows(IllegalArgumentException.class,
                () -> mSession.launchApplication(UNDECLARED_TARGET_PACKAGE));
    }

    @Test
    public void tap_sendsTouchscreenEvents() throws Exception {
        createComputerControlSession(mDefaultParams);
        mSession.tap(60, 200);
        verify(mVirtualTouchscreen).sendTouchEvent(argThat(
                new MatchesTouchEvent(60, 200, VirtualTouchEvent.ACTION_DOWN)));
@@ -234,6 +347,7 @@ public class ComputerControlSessionTest {

    @Test
    public void swipe_sendsTouchscreenEvents() throws Exception {
        createComputerControlSession(mDefaultParams);
        mSession.swipe(60, 200, 180, 400);
        verify(mVirtualTouchscreen).sendTouchEvent(argThat(
                new MatchesTouchEvent(60, 200, VirtualTouchEvent.ACTION_DOWN)));
@@ -251,6 +365,27 @@ public class ComputerControlSessionTest {
                new MatchesTouchEvent(180, 400, VirtualTouchEvent.ACTION_UP)));
    }

    private void createComputerControlSession(ComputerControlSessionParams params) {
        mSession = new ComputerControlSessionImpl(mAppToken, params,
                AttributionSource.myAttributionSource(), mVirtualDeviceFactory, mOnClosedListener,
                mInjector);
    }

    private static class MatchesActivityPolicyExcemption implements
            ArgumentMatcher<ActivityPolicyExemption> {

        private final String mPackageName;

        MatchesActivityPolicyExcemption(String packageName) {
            mPackageName = packageName;
        }

        @Override
        public boolean matches(ActivityPolicyExemption argument) {
            return mPackageName.equals(argument.getPackageName());
        }
    }

    private static class MatchesTouchEvent implements ArgumentMatcher<VirtualTouchEvent> {

        private final int mX;