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

Commit 785a118f authored by Prabir Pradhan's avatar Prabir Pradhan Committed by Automerger Merge Worker
Browse files

Make virtual input device creation synchronous am: eb2d1e65

parents f912bc18 eb2d1e65
Loading
Loading
Loading
Loading
+166 −53
Original line number Original line Diff line number Diff line
@@ -22,6 +22,7 @@ import android.annotation.StringDef;
import android.graphics.Point;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.PointF;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualKeyEvent;
@@ -29,11 +30,13 @@ import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchEvent;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.Slog;
import android.view.Display;
import android.view.Display;
import android.view.InputDevice;


import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
@@ -44,7 +47,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.RetentionPolicy;
import java.util.Iterator;
import java.util.Iterator;
import java.util.Map;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;


/** Controls virtual input devices, including device lifecycle and event dispatch. */
/** Controls virtual input devices, including device lifecycle and event dispatch. */
class InputController {
class InputController {
@@ -72,20 +79,27 @@ class InputController {
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
    final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();


    private final Handler mHandler;
    private final NativeWrapper mNativeWrapper;
    private final NativeWrapper mNativeWrapper;
    private final DisplayManagerInternal mDisplayManagerInternal;
    private final DisplayManagerInternal mDisplayManagerInternal;
    private final InputManagerInternal mInputManagerInternal;
    private final InputManagerInternal mInputManagerInternal;
    private final DeviceCreationThreadVerifier mThreadVerifier;


    InputController(@NonNull Object lock) {
    InputController(@NonNull Object lock, @NonNull Handler handler) {
        this(lock, new NativeWrapper());
        this(lock, new NativeWrapper(), handler,
                // Verify that virtual devices are not created on the handler thread.
                () -> !handler.getLooper().isCurrentThread());
    }
    }


    @VisibleForTesting
    @VisibleForTesting
    InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) {
    InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper,
            @NonNull Handler handler, @NonNull DeviceCreationThreadVerifier threadVerifier) {
        mLock = lock;
        mLock = lock;
        mHandler = handler;
        mNativeWrapper = nativeWrapper;
        mNativeWrapper = nativeWrapper;
        mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
        mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
        mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
        mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
        mThreadVerifier = threadVerifier;
    }
    }


    void close() {
    void close() {
@@ -108,23 +122,13 @@ class InputController {
            @NonNull IBinder deviceToken,
            @NonNull IBinder deviceToken,
            int displayId) {
            int displayId) {
        final String phys = createPhys(PHYS_TYPE_KEYBOARD);
        final String phys = createPhys(PHYS_TYPE_KEYBOARD);
        setUniqueIdAssociation(displayId, phys);
        final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys);
        if (fd < 0) {
            throw new RuntimeException(
                    "A native error occurred when creating keyboard: " + -fd);
        }
        final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
        synchronized (mLock) {
            mInputDeviceDescriptors.put(deviceToken,
                    new InputDeviceDescriptor(fd, binderDeathRecipient,
                            InputDeviceDescriptor.TYPE_KEYBOARD, displayId, phys));
        }
        try {
        try {
            deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
            createDeviceInternal(InputDeviceDescriptor.TYPE_KEYBOARD, deviceName, vendorId,
        } catch (RemoteException e) {
                    productId, deviceToken, displayId, phys,
            // TODO(b/215608394): remove and close InputDeviceDescriptor
                    () -> mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys));
            throw new RuntimeException("Could not create virtual keyboard", e);
        } catch (DeviceCreationException e) {
            throw new RuntimeException(
                    "Failed to create virtual keyboard device '" + deviceName + "'.", e);
        }
        }
    }
    }


@@ -134,26 +138,16 @@ class InputController {
            @NonNull IBinder deviceToken,
            @NonNull IBinder deviceToken,
            int displayId) {
            int displayId) {
        final String phys = createPhys(PHYS_TYPE_MOUSE);
        final String phys = createPhys(PHYS_TYPE_MOUSE);
        setUniqueIdAssociation(displayId, phys);
        try {
        final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys);
            createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
        if (fd < 0) {
                    deviceToken, displayId, phys,
                    () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
        } catch (DeviceCreationException e) {
            throw new RuntimeException(
            throw new RuntimeException(
                    "A native error occurred when creating mouse: " + -fd);
                    "Failed to create virtual mouse device: '" + deviceName + "'.", e);
        }
        }
        final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
        synchronized (mLock) {
            mInputDeviceDescriptors.put(deviceToken,
                    new InputDeviceDescriptor(fd, binderDeathRecipient,
                            InputDeviceDescriptor.TYPE_MOUSE, displayId, phys));
        mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
        mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
    }
    }
        try {
            deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
        } catch (RemoteException e) {
            // TODO(b/215608394): remove and close InputDeviceDescriptor
            throw new RuntimeException("Could not create virtual mouse", e);
        }
    }


    void createTouchscreen(@NonNull String deviceName,
    void createTouchscreen(@NonNull String deviceName,
            int vendorId,
            int vendorId,
@@ -162,24 +156,14 @@ class InputController {
            int displayId,
            int displayId,
            @NonNull Point screenSize) {
            @NonNull Point screenSize) {
        final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
        final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
        setUniqueIdAssociation(displayId, phys);
        final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys,
                screenSize.y, screenSize.x);
        if (fd < 0) {
            throw new RuntimeException(
                    "A native error occurred when creating touchscreen: " + -fd);
        }
        final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
        synchronized (mLock) {
            mInputDeviceDescriptors.put(deviceToken,
                    new InputDeviceDescriptor(fd, binderDeathRecipient,
                            InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId, phys));
        }
        try {
        try {
            deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
            createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId,
        } catch (RemoteException e) {
                    productId, deviceToken, displayId, phys,
            // TODO(b/215608394): remove and close InputDeviceDescriptor
                    () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
            throw new RuntimeException("Could not create virtual touchscreen", e);
                            phys, screenSize.y, screenSize.x));
        } catch (DeviceCreationException e) {
            throw new RuntimeException(
                    "Failed to create virtual touchscreen device '" + deviceName + "'.", e);
        }
        }
    }
    }


@@ -510,4 +494,133 @@ class InputController {
            unregisterInputDevice(mDeviceToken);
            unregisterInputDevice(mDeviceToken);
        }
        }
    }
    }

    /** A helper class used to wait for an input device to be registered. */
    private class WaitForDevice implements AutoCloseable {
        private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1);
        private final InputManager.InputDeviceListener mListener;

        WaitForDevice(String deviceName, int vendorId, int productId) {
            mListener = new InputManager.InputDeviceListener() {
                @Override
                public void onInputDeviceAdded(int deviceId) {
                    final InputDevice device = InputManager.getInstance().getInputDevice(
                            deviceId);
                    Objects.requireNonNull(device, "Newly added input device was null.");
                    if (!device.getName().equals(deviceName)) {
                        return;
                    }
                    final InputDeviceIdentifier id = device.getIdentifier();
                    if (id.getVendorId() != vendorId || id.getProductId() != productId) {
                        return;
                    }
                    mDeviceAddedLatch.countDown();
                }

                @Override
                public void onInputDeviceRemoved(int deviceId) {

                }

                @Override
                public void onInputDeviceChanged(int deviceId) {

                }
            };
            InputManager.getInstance().registerInputDeviceListener(mListener, mHandler);
        }

        /** Note: This must not be called from {@link #mHandler}'s thread. */
        void waitForDeviceCreation() throws DeviceCreationException {
            try {
                if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) {
                    throw new DeviceCreationException(
                            "Timed out waiting for virtual device to be created.");
                }
            } catch (InterruptedException e) {
                throw new DeviceCreationException(
                        "Interrupted while waiting for virtual device to be created.", e);
            }
        }

        @Override
        public void close() {
            InputManager.getInstance().unregisterInputDeviceListener(mListener);
        }
    }

    /** An internal exception that is thrown to indicate an error when opening a virtual device. */
    private static class DeviceCreationException extends Exception {
        DeviceCreationException(String message) {
            super(message);
        }
        DeviceCreationException(String message, Exception cause) {
            super(message, cause);
        }
    }

    /**
     * Creates a virtual input device synchronously, and waits for the notification that the device
     * was added.
     *
     * Note: Input device creation is expected to happen on a binder thread, and the calling thread
     * will be blocked until the input device creation is successful. This should not be called on
     * the handler's thread.
     *
     * @throws DeviceCreationException Throws this exception if anything unexpected happens in the
     *                                 process of creating the device. This method will take care
     *                                 to restore the state of the system in the event of any
     *                                 unexpected behavior.
     */
    private void createDeviceInternal(@InputDeviceDescriptor.Type int type, String deviceName,
            int vendorId, int productId, IBinder deviceToken, int displayId, String phys,
            Supplier<Integer> deviceOpener)
            throws DeviceCreationException {
        if (!mThreadVerifier.isValidThread()) {
            throw new IllegalStateException(
                    "Virtual device creation should happen on an auxiliary thread (e.g. binder "
                            + "thread) and not from the handler's thread.");
        }

        final int fd;
        final BinderDeathRecipient binderDeathRecipient;

        setUniqueIdAssociation(displayId, phys);
        try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) {
            fd = deviceOpener.get();
            if (fd < 0) {
                throw new DeviceCreationException(
                        "A native error occurred when creating touchscreen: " + -fd);
            }
            // The fd is valid from here, so ensure that all failures close the fd after this point.
            try {
                waiter.waitForDeviceCreation();

                binderDeathRecipient = new BinderDeathRecipient(deviceToken);
                try {
                    deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
                } catch (RemoteException e) {
                    throw new DeviceCreationException(
                            "Client died before virtual device could be created.", e);
                }
            } catch (DeviceCreationException e) {
                mNativeWrapper.closeUinput(fd);
                throw e;
            }
        } catch (DeviceCreationException e) {
            InputManager.getInstance().removeUniqueIdAssociation(phys);
            throw e;
        }

        synchronized (mLock) {
            mInputDeviceDescriptors.put(deviceToken,
                    new InputDeviceDescriptor(fd, binderDeathRecipient, type, displayId, phys));
        }
    }

    @VisibleForTesting
    interface DeviceCreationThreadVerifier {
        /** Returns true if the calling thread is a valid thread for device creation. */
        boolean isValidThread();
    }
}
}
+2 −1
Original line number Original line Diff line number Diff line
@@ -166,7 +166,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
        mAppToken = token;
        mAppToken = token;
        mParams = params;
        mParams = params;
        if (inputController == null) {
        if (inputController == null) {
            mInputController = new InputController(mVirtualDeviceLock);
            mInputController = new InputController(
                    mVirtualDeviceLock, context.getMainThreadHandler());
        } else {
        } else {
            mInputController = inputController;
            mInputController = inputController;
        }
        }
+16 −8
Original line number Original line Diff line number Diff line
@@ -21,20 +21,21 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;


import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.InputManagerInternal;
import android.os.Binder;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.DisplayInfo;


import androidx.test.runner.AndroidJUnit4;

import com.android.server.LocalServices;
import com.android.server.LocalServices;


import org.junit.Before;
import org.junit.Before;
@@ -44,7 +45,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoAnnotations;


@Presubmit
@Presubmit
@RunWith(AndroidJUnit4.class)
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class InputControllerTest {
public class InputControllerTest {


    @Mock
    @Mock
@@ -56,11 +58,14 @@ public class InputControllerTest {
    @Mock
    @Mock
    private IInputManager mIInputManagerMock;
    private IInputManager mIInputManagerMock;


    private InputManagerMockHelper mInputManagerMockHelper;
    private InputController mInputController;
    private InputController mInputController;


    @Before
    @Before
    public void setUp() throws Exception {
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        MockitoAnnotations.initMocks(this);
        mInputManagerMockHelper = new InputManagerMockHelper(
                TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);


        doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
        doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
        LocalServices.removeServiceForTest(InputManagerInternal.class);
        LocalServices.removeServiceForTest(InputManagerInternal.class);
@@ -72,10 +77,10 @@ public class InputControllerTest {
        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);


        InputManager.resetInstance(mIInputManagerMock);
        // Allow virtual devices to be created on the looper thread for testing.
        doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
        final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
        doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
        mInputController = new InputController(new Object(), mNativeWrapperMock,
        mInputController = new InputController(new Object(), mNativeWrapperMock);
                new Handler(TestableLooper.get(this).getLooper()), threadVerifier);
    }
    }


    @Test
    @Test
@@ -83,6 +88,7 @@ public class InputControllerTest {
        final IBinder deviceToken = new Binder();
        final IBinder deviceToken = new Binder();
        mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
        mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                /* displayId= */ 1);
                /* displayId= */ 1);
        verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
        doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
        doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
        mInputController.unregisterInputDevice(deviceToken);
        mInputController.unregisterInputDevice(deviceToken);
@@ -95,10 +101,12 @@ public class InputControllerTest {
        final IBinder deviceToken = new Binder();
        final IBinder deviceToken = new Binder();
        mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
        mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                /* displayId= */ 1);
                /* displayId= */ 1);
        verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
        final IBinder deviceToken2 = new Binder();
        final IBinder deviceToken2 = new Binder();
        mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
        mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
                /* displayId= */ 2);
                /* displayId= */ 2);
        verify(mNativeWrapperMock, times(2)).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
        mInputController.unregisterInputDevice(deviceToken);
        mInputController.unregisterInputDevice(deviceToken);
        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
+101 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 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 org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;

import android.hardware.input.IInputDevicesChangedListener;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManager;
import android.os.RemoteException;
import android.testing.TestableLooper;
import android.view.InputDevice;

import org.mockito.invocation.InvocationOnMock;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;

/**
 * A test utility class used to share the logic for setting up {@link InputManager}'s callback for
 * when a virtual input device being added.
 */
class InputManagerMockHelper {
    private final TestableLooper mTestableLooper;
    private final InputController.NativeWrapper mNativeWrapperMock;
    private final IInputManager mIInputManagerMock;
    private final List<InputDevice> mDevices = new ArrayList<>();
    private IInputDevicesChangedListener mDevicesChangedListener;

    InputManagerMockHelper(TestableLooper testableLooper,
            InputController.NativeWrapper nativeWrapperMock, IInputManager iInputManagerMock)
            throws Exception {
        mTestableLooper = testableLooper;
        mNativeWrapperMock = nativeWrapperMock;
        mIInputManagerMock = iInputManagerMock;

        doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputMouse(
                anyString(), anyInt(), anyInt(), anyString());
        doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputKeyboard(
                anyString(), anyInt(), anyInt(), anyString());
        doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputTouchscreen(
                anyString(), anyInt(), anyInt(), anyString(), anyInt(), anyInt());

        doAnswer(inv -> {
            mDevicesChangedListener = inv.getArgument(0);
            return null;
        }).when(mIInputManagerMock).registerInputDevicesChangedListener(notNull());
        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
        doAnswer(inv -> mDevices.get(inv.getArgument(0)))
                .when(mIInputManagerMock).getInputDevice(anyInt());
        doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
        doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());

        // Set a new instance of InputManager for testing that uses the IInputManager mock as the
        // interface to the server.
        InputManager.resetInstance(mIInputManagerMock);
    }

    private Void handleNativeOpenInputDevice(InvocationOnMock inv) {
        Objects.requireNonNull(mDevicesChangedListener,
                "InputController did not register an InputDevicesChangedListener.");
        // We only use a subset of the fields of InputDevice in InputController.
        final InputDevice device = new InputDevice(mDevices.size() /*id*/, 1 /*generation*/, 0,
                inv.getArgument(0) /*name*/, inv.getArgument(1) /*vendorId*/,
                inv.getArgument(2) /*productId*/, inv.getArgument(3) /*descriptor*/,
                true /*isExternal*/, 0 /*sources*/, 0 /*keyboardType*/,
                null /*keyCharacterMap*/, false /*hasVibrator*/, false /*hasMic*/,
                false /*hasButtonUnderPad*/, false /*hasSensor*/, false /*hasBattery*/);
        mDevices.add(device);
        try {
            mDevicesChangedListener.onInputDevicesChanged(
                    mDevices.stream().flatMapToInt(
                            d -> IntStream.of(d.getId(), d.getGeneration())).toArray());
        } catch (RemoteException ignored) {
        }
        // Process the device added notification.
        mTestableLooper.processAllMessages();
        return null;
    }
}
+12 −2
Original line number Original line Diff line number Diff line
@@ -54,6 +54,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.graphics.Point;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseButtonEvent;
@@ -118,6 +119,7 @@ public class VirtualDeviceManagerServiceTest {
    private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
    private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;


    private Context mContext;
    private Context mContext;
    private InputManagerMockHelper mInputManagerMockHelper;
    private VirtualDeviceImpl mDeviceImpl;
    private VirtualDeviceImpl mDeviceImpl;
    private InputController mInputController;
    private InputController mInputController;
    private AssociationInfo mAssociationInfo;
    private AssociationInfo mAssociationInfo;
@@ -146,6 +148,8 @@ public class VirtualDeviceManagerServiceTest {
    private IAudioConfigChangedCallback mConfigChangedCallback;
    private IAudioConfigChangedCallback mConfigChangedCallback;
    @Mock
    @Mock
    private ApplicationInfo mApplicationInfoMock;
    private ApplicationInfo mApplicationInfoMock;
    @Mock
    IInputManager mIInputManagerMock;


    private ArraySet<ComponentName> getBlockedActivities() {
    private ArraySet<ComponentName> getBlockedActivities() {
        ArraySet<ComponentName> blockedActivities = new ArraySet<>();
        ArraySet<ComponentName> blockedActivities = new ArraySet<>();
@@ -170,7 +174,7 @@ public class VirtualDeviceManagerServiceTest {
    }
    }


    @Before
    @Before
    public void setUp() {
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        MockitoAnnotations.initMocks(this);


        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
@@ -199,7 +203,13 @@ public class VirtualDeviceManagerServiceTest {
                new Handler(TestableLooper.get(this).getLooper()));
                new Handler(TestableLooper.get(this).getLooper()));
        when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
        when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);


        mInputController = new InputController(new Object(), mNativeWrapperMock);
        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()), threadVerifier);

        mAssociationInfo = new AssociationInfo(1, 0, null,
        mAssociationInfo = new AssociationInfo(1, 0, null,
                MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
                MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);