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

Commit 7c507123 authored by Marvin Ramin's avatar Marvin Ramin
Browse files

Implement ComputerControl Activity policy

Results in us having three possible policies with their respective flags:
- None: Everything, minus PermissionController is accessible
- Relaxed: All apps specified in the params + any application that has
  no launcher Intent are accessible.
- Strict: Only apps specified in the params are accessible.

Bug: 437849470
Flag: android.companion.virtualdevice.flags.computer_control_activity_policy_strict
Test: atest ComputerControlSessionTest
Change-Id: I7516dcd77cb9d29effd8d977ae064be306c95d30
parent 106985e5
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;