blob: f242fda15f066e56d61603f552187eb5e721630e [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.companion.virtual;
import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
import android.Manifest;
import android.app.WindowConfiguration;
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
import android.net.MacAddress;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.IPowerManager;
import android.os.IThermalService;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArraySet;
import android.view.DisplayInfo;
import android.view.KeyEvent;
import android.view.WindowManager;
import androidx.test.InstrumentationRegistry;
import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.server.LocalServices;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Consumer;
@Presubmit
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class VirtualDeviceManagerServiceTest {
private static final String NONBLOCKED_APP_PACKAGE_NAME = "com.someapp";
private static final String PERMISSION_CONTROLLER_PACKAGE_NAME =
"com.android.permissioncontroller";
private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
private static final String VENDING_PACKAGE_NAME = "com.android.vending";
private static final String GOOGLE_DIALER_PACKAGE_NAME = "com.google.android.dialer";
private static final String GOOGLE_MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
private static final String DEVICE_NAME = "device name";
private static final int DISPLAY_ID = 2;
private static final int PRODUCT_ID = 10;
private static final int VENDOR_ID = 5;
private static final String UNIQUE_ID = "uniqueid";
private static final String PHYS = "phys";
private static final int HEIGHT = 1800;
private static final int WIDTH = 900;
private static final Binder BINDER = new Binder("binder");
private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
private Context mContext;
private InputManagerMockHelper mInputManagerMockHelper;
private VirtualDeviceImpl mDeviceImpl;
private InputController mInputController;
private AssociationInfo mAssociationInfo;
@Mock
private InputController.NativeWrapper mNativeWrapperMock;
@Mock
private DisplayManagerInternal mDisplayManagerInternalMock;
@Mock
private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
@Mock
private DevicePolicyManager mDevicePolicyManagerMock;
@Mock
private InputManagerInternal mInputManagerInternalMock;
@Mock
private IVirtualDeviceActivityListener mActivityListener;
@Mock
private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
@Mock
IPowerManager mIPowerManagerMock;
@Mock
IThermalService mIThermalServiceMock;
private PowerManager mPowerManager;
@Mock
private IAudioRoutingCallback mRoutingCallback;
@Mock
private IAudioConfigChangedCallback mConfigChangedCallback;
@Mock
private ApplicationInfo mApplicationInfoMock;
@Mock
IInputManager mIInputManagerMock;
private ArraySet<ComponentName> getBlockedActivities() {
ArraySet<ComponentName> blockedActivities = new ArraySet<>();
blockedActivities.add(new ComponentName(SETTINGS_PACKAGE_NAME, SETTINGS_PACKAGE_NAME));
blockedActivities.add(new ComponentName(VENDING_PACKAGE_NAME, VENDING_PACKAGE_NAME));
blockedActivities.add(
new ComponentName(GOOGLE_DIALER_PACKAGE_NAME, GOOGLE_DIALER_PACKAGE_NAME));
blockedActivities.add(
new ComponentName(GOOGLE_MAPS_PACKAGE_NAME, GOOGLE_MAPS_PACKAGE_NAME));
return blockedActivities;
}
private ArrayList<ActivityInfo> getActivityInfoList(
String packageName, String name, boolean displayOnRemoveDevices) {
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.packageName = packageName;
activityInfo.name = name;
activityInfo.flags = displayOnRemoveDevices
? FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES : FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES;
activityInfo.applicationInfo = mApplicationInfoMock;
return new ArrayList<>(Arrays.asList(activityInfo));
}
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.uniqueId = UNIQUE_ID;
doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
doReturn(mContext).when(mContext).createContextAsUser(eq(Process.myUserHandle()), anyInt());
doNothing().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
mDevicePolicyManagerMock);
mPowerManager = new PowerManager(mContext, mIPowerManagerMock, mIThermalServiceMock,
new Handler(TestableLooper.get(this).getLooper()));
when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
mInputManagerMockHelper = new InputManagerMockHelper(
TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
// Allow virtual devices to be created on the looper thread for testing.
final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
mInputController = new InputController(new Object(), mNativeWrapperMock,
new Handler(TestableLooper.get(this).getLooper()),
mContext.getSystemService(WindowManager.class), threadVerifier);
mAssociationInfo = new AssociationInfo(1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
VirtualDeviceParams params = new VirtualDeviceParams
.Builder()
.setBlockedActivities(getBlockedActivities())
.build();
mDeviceImpl = new VirtualDeviceImpl(mContext,
mAssociationInfo, new Binder(), /* uid */ 0, mInputController,
(int associationId) -> {
}, mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback,
params);
}
@Test
public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
// This call should not throw any exceptions.
mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID);
}
@Test
public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException {
verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
nullable(String.class), anyInt(), eq(null));
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
nullable(String.class), eq(DISPLAY_ID), eq(null));
}
@Test
public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
throws RemoteException {
GenericWindowPolicyController gwpc = mDeviceImpl.createWindowPolicyController();
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
assertThrows(IllegalStateException.class,
() -> mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID));
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
nullable(String.class), eq(DISPLAY_ID), eq(null));
}
@Test
public void onVirtualDisplayRemovedLocked_unknownDisplayId_throwsException() {
final int unknownDisplayId = 999;
assertThrows(IllegalStateException.class,
() -> mDeviceImpl.onVirtualDisplayRemovedLocked(unknownDisplayId));
}
@Test
public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
nullable(String.class), eq(DISPLAY_ID), eq(null));
IBinder wakeLock = wakeLockCaptor.getValue();
mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID);
verify(mIPowerManagerMock, Mockito.times(1)).releaseWakeLock(eq(wakeLock), anyInt());
}
@Test
public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
nullable(String.class), eq(DISPLAY_ID), eq(null));
IBinder wakeLock = wakeLockCaptor.getValue();
// Close the VirtualDevice without first notifying it of the VirtualDisplay removal.
mDeviceImpl.close();
verify(mIPowerManagerMock, Mockito.times(1)).releaseWakeLock(eq(wakeLock), anyInt());
}
@Test
public void createVirtualKeyboard_noDisplay_failsSecurityException() {
assertThrows(
SecurityException.class,
() -> mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
PRODUCT_ID, BINDER));
}
@Test
public void createVirtualMouse_noDisplay_failsSecurityException() {
assertThrows(
SecurityException.class,
() -> mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
PRODUCT_ID, BINDER));
}
@Test
public void createVirtualTouchscreen_noDisplay_failsSecurityException() {
assertThrows(
SecurityException.class,
() -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME,
VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)));
}
@Test
public void onAudioSessionStarting_noDisplay_failsSecurityException() {
assertThrows(SecurityException.class,
() -> mDeviceImpl.onAudioSessionStarting(
DISPLAY_ID, mRoutingCallback, mConfigChangedCallback));
}
@Test
public void createVirtualKeyboard_noPermission_failsSecurityException() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
assertThrows(
SecurityException.class,
() -> mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
PRODUCT_ID, BINDER));
}
@Test
public void createVirtualMouse_noPermission_failsSecurityException() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
assertThrows(
SecurityException.class,
() -> mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
PRODUCT_ID, BINDER));
}
@Test
public void createVirtualTouchscreen_noPermission_failsSecurityException() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
assertThrows(
SecurityException.class,
() -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME,
VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)));
}
@Test
public void onAudioSessionStarting_noPermission_failsSecurityException() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
assertThrows(SecurityException.class,
() -> mDeviceImpl.onAudioSessionStarting(
DISPLAY_ID, mRoutingCallback, mConfigChangedCallback));
}
@Test
public void onAudioSessionEnded_noPermission_failsSecurityException() {
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded());
}
@Test
public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
verify(mNativeWrapperMock).openUinputKeyboard(eq(DEVICE_NAME), eq(VENDOR_ID),
eq(PRODUCT_ID), anyString());
}
@Test
public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
verify(mNativeWrapperMock).openUinputMouse(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID),
anyString());
}
@Test
public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
BINDER, new Point(WIDTH, HEIGHT));
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.mInputDeviceDescriptors).isNotEmpty();
verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
}
@Test
public void onAudioSessionStarting_hasVirtualAudioController() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNotNull();
}
@Test
public void onAudioSessionEnded_noVirtualAudioController() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
mDeviceImpl.onAudioSessionEnded();
assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNull();
}
@Test
public void close_cleanVirtualAudioController() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
mDeviceImpl.close();
assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNull();
}
@Test
public void sendKeyEvent_noFd() {
assertThrows(
IllegalArgumentException.class,
() ->
mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder()
.setKeyCode(KeyEvent.KEYCODE_A)
.setAction(VirtualKeyEvent.ACTION_DOWN).build()));
}
@Test
public void sendKeyEvent_hasFd_writesEvent() {
final int fd = 1;
final int keyCode = KeyEvent.KEYCODE_A;
final int action = VirtualKeyEvent.ACTION_UP;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1,
/* displayId= */ 1, PHYS));
mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode)
.setAction(action).build());
verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action);
}
@Test
public void sendButtonEvent_noFd() {
assertThrows(
IllegalArgumentException.class,
() ->
mDeviceImpl.sendButtonEvent(BINDER,
new VirtualMouseButtonEvent.Builder()
.setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK)
.setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS)
.build()));
}
@Test
public void sendButtonEvent_hasFd_writesEvent() {
final int fd = 1;
final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK;
final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
/* displayId= */ 1, PHYS));
doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
.setButtonCode(buttonCode)
.setAction(action).build());
verify(mNativeWrapperMock).writeButtonEvent(fd, buttonCode, action);
}
@Test
public void sendButtonEvent_hasFd_wrongDisplay_throwsIllegalStateException() {
final int fd = 1;
final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK;
final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
/* displayId= */ 1, PHYS));
assertThrows(
IllegalStateException.class,
() ->
mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
.setButtonCode(buttonCode)
.setAction(action).build()));
}
@Test
public void sendRelativeEvent_noFd() {
assertThrows(
IllegalArgumentException.class,
() ->
mDeviceImpl.sendRelativeEvent(BINDER,
new VirtualMouseRelativeEvent.Builder().setRelativeX(
0.0f).setRelativeY(0.0f).build()));
}
@Test
public void sendRelativeEvent_hasFd_writesEvent() {
final int fd = 1;
final float x = -0.2f;
final float y = 0.7f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
/* displayId= */ 1, PHYS));
doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder()
.setRelativeX(x).setRelativeY(y).build());
verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y);
}
@Test
public void sendRelativeEvent_hasFd_wrongDisplay_throwsIllegalStateException() {
final int fd = 1;
final float x = -0.2f;
final float y = 0.7f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
/* displayId= */ 1, PHYS));
assertThrows(
IllegalStateException.class,
() ->
mDeviceImpl.sendRelativeEvent(BINDER,
new VirtualMouseRelativeEvent.Builder()
.setRelativeX(x).setRelativeY(y).build()));
}
@Test
public void sendScrollEvent_noFd() {
assertThrows(
IllegalArgumentException.class,
() ->
mDeviceImpl.sendScrollEvent(BINDER,
new VirtualMouseScrollEvent.Builder()
.setXAxisMovement(-1f)
.setYAxisMovement(1f).build()));
}
@Test
public void sendScrollEvent_hasFd_writesEvent() {
final int fd = 1;
final float x = 0.5f;
final float y = 1f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
/* displayId= */ 1, PHYS));
doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
.setXAxisMovement(x)
.setYAxisMovement(y).build());
verify(mNativeWrapperMock).writeScrollEvent(fd, x, y);
}
@Test
public void sendScrollEvent_hasFd_wrongDisplay_throwsIllegalStateException() {
final int fd = 1;
final float x = 0.5f;
final float y = 1f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
/* displayId= */ 1, PHYS));
assertThrows(
IllegalStateException.class,
() ->
mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
.setXAxisMovement(x)
.setYAxisMovement(y).build()));
}
@Test
public void sendTouchEvent_noFd() {
assertThrows(
IllegalArgumentException.class,
() ->
mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder()
.setX(0.0f)
.setY(0.0f)
.setAction(VirtualTouchEvent.ACTION_UP)
.setPointerId(1)
.setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
.build()));
}
@Test
public void sendTouchEvent_hasFd_writesEvent_withoutPressureOrMajorAxisSize() {
final int fd = 1;
final int pointerId = 5;
final int toolType = VirtualTouchEvent.TOOL_TYPE_FINGER;
final float x = 100.5f;
final float y = 200.5f;
final int action = VirtualTouchEvent.ACTION_UP;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
/* displayId= */ 1, PHYS));
mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
.setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build());
verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,
Float.NaN);
}
@Test
public void sendTouchEvent_hasFd_writesEvent() {
final int fd = 1;
final int pointerId = 5;
final int toolType = VirtualTouchEvent.TOOL_TYPE_FINGER;
final float x = 100.5f;
final float y = 200.5f;
final int action = VirtualTouchEvent.ACTION_UP;
final float pressure = 1.0f;
final float majorAxisSize = 10.0f;
mInputController.mInputDeviceDescriptors.put(BINDER,
new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
/* displayId= */ 1, PHYS));
mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
.setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType)
.setPressure(pressure).setMajorAxisSize(majorAxisSize).build());
verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, pressure,
majorAxisSize);
}
@Test
public void setShowPointerIcon_setsValueForAllDisplays() {
mDeviceImpl.mVirtualDisplayIds.add(1);
mDeviceImpl.mVirtualDisplayIds.add(2);
mDeviceImpl.mVirtualDisplayIds.add(3);
mDeviceImpl.createVirtualMouse(1, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER);
mDeviceImpl.createVirtualMouse(2, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER);
mDeviceImpl.createVirtualMouse(3, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER);
mDeviceImpl.setShowPointerIcon(false);
verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(false), anyInt());
verify(mInputManagerInternalMock, never()).setPointerIconVisible(eq(true), anyInt());
mDeviceImpl.setShowPointerIcon(true);
verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(true), anyInt());
}
@Test
public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
DISPLAY_ID);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
NONBLOCKED_APP_PACKAGE_NAME,
NONBLOCKED_APP_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfos.get(0), mAssociationInfo.getDisplayName());
gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
verify(mContext, never()).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
@Test
public void openPermissionControllerOnVirtualDisplay_startBlockedAlertActivity() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
DISPLAY_ID);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
PERMISSION_CONTROLLER_PACKAGE_NAME,
PERMISSION_CONTROLLER_PACKAGE_NAME, /* displayOnRemoveDevices */ false);
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfos.get(0), mAssociationInfo.getDisplayName());
gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
@Test
public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
DISPLAY_ID);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
SETTINGS_PACKAGE_NAME,
SETTINGS_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfos.get(0), mAssociationInfo.getDisplayName());
gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
@Test
public void openVendingOnVirtualDisplay_startBlockedAlertActivity() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
DISPLAY_ID);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
VENDING_PACKAGE_NAME,
VENDING_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfos.get(0), mAssociationInfo.getDisplayName());
gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
@Test
public void openGoogleDialerOnVirtualDisplay_startBlockedAlertActivity() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
DISPLAY_ID);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
GOOGLE_DIALER_PACKAGE_NAME,
GOOGLE_DIALER_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfos.get(0), mAssociationInfo.getDisplayName());
gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
@Test
public void openGoogleMapsOnVirtualDisplay_startBlockedAlertActivity() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
DISPLAY_ID);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
GOOGLE_MAPS_PACKAGE_NAME,
GOOGLE_MAPS_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfos.get(0), mAssociationInfo.getDisplayName());
gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
}