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

Commit 2557acb5 authored by Antony Sargent's avatar Antony Sargent Committed by Android (Google) Code Review
Browse files

Merge "Keep VirtualDevice-created VirtualDisplays awake"

parents 0e688c82 c63f77ca
Loading
Loading
Loading
Loading
+70 −22
Original line number Diff line number Diff line
@@ -45,10 +45,12 @@ import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -59,6 +61,7 @@ import com.android.server.LocalServices;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Map;
import java.util.Set;


@@ -78,6 +81,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
    private final OnDeviceCloseListener mListener;
    private final IBinder mAppToken;
    private final VirtualDeviceParams mParams;
    private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
    private final IVirtualDeviceActivityListener mActivityListener;

    private ActivityListener createListenerAdapter(int displayId) {
@@ -206,6 +210,16 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub

    @Override // Binder call
    public void close() {
        synchronized (mVirtualDeviceLock) {
            if (!mPerDisplayWakelocks.isEmpty()) {
                mPerDisplayWakelocks.forEach((displayId, wakeLock) -> {
                    Slog.w(TAG, "VirtualDisplay " + displayId + " owned by UID " + mOwnerUid
                            + " was not properly released");
                    wakeLock.release();
                });
                mPerDisplayWakelocks.clear();
            }
        }
        mListener.onClose(mAssociationInfo.getId());
        mAppToken.unlinkToDeath(this, 0);
        mInputController.close();
@@ -383,13 +397,21 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
    }

    DisplayWindowPolicyController onVirtualDisplayCreatedLocked(int displayId) {
        synchronized (mVirtualDeviceLock) {
            if (mVirtualDisplayIds.contains(displayId)) {
                throw new IllegalStateException(
                        "Virtual device already have a virtual display with ID " + displayId);
            }
            mVirtualDisplayIds.add(displayId);
        LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture(
                displayId, false);

            // Since we're being called in the middle of the display being created, we post a
            // task to grab the wakelock instead of doing it synchronously here, to avoid
            // reentrancy  problems.
            mContext.getMainThreadHandler().post(() -> addWakeLockForDisplay(displayId));

            LocalServices.getService(
                    InputManagerInternal.class).setDisplayEligibilityForPointerCapture(displayId,
                    false);
            final GenericWindowPolicyController dwpc =
                    new GenericWindowPolicyController(FLAG_SECURE,
                            SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
@@ -400,6 +422,24 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
            mWindowPolicyControllers.put(displayId, dwpc);
            return dwpc;
        }
    }

    void addWakeLockForDisplay(int displayId) {
        synchronized (mVirtualDeviceLock) {
            if (!mVirtualDisplayIds.contains(displayId)
                    || mPerDisplayWakelocks.containsKey(displayId)) {
                Slog.e(TAG, "Not creating wakelock for displayId " + displayId);
                return;
            }
            PowerManager powerManager = mContext.getSystemService(PowerManager.class);
            PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
                    PowerManager.SCREEN_BRIGHT_WAKE_LOCK
                            | PowerManager.ACQUIRE_CAUSES_WAKEUP,
                    TAG + ":" + displayId, displayId);
            wakeLock.acquire();
            mPerDisplayWakelocks.put(displayId, wakeLock);
        }
    }

    private ArraySet<UserHandle> getAllowedUserHandles() {
        ArraySet<UserHandle> result = new ArraySet<>();
@@ -420,15 +460,23 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
    }

    void onVirtualDisplayRemovedLocked(int displayId) {
        synchronized (mVirtualDeviceLock) {
            if (!mVirtualDisplayIds.contains(displayId)) {
                throw new IllegalStateException(
                        "Virtual device doesn't have a virtual display with ID " + displayId);
            }
            PowerManager.WakeLock wakeLock = mPerDisplayWakelocks.get(displayId);
            if (wakeLock != null) {
                wakeLock.release();
                mPerDisplayWakelocks.remove(displayId);
            }
            mVirtualDisplayIds.remove(displayId);
        LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture(
            LocalServices.getService(
                    InputManagerInternal.class).setDisplayEligibilityForPointerCapture(
                    displayId, true);
            mWindowPolicyControllers.remove(displayId);
        }
    }

    int getOwnerUid() {
        return mOwnerUid;
+97 −4
Original line number Diff line number Diff line
@@ -18,17 +18,21 @@ package com.android.server.companion.virtual;

import static com.google.common.truth.Truth.assertWithMessage;

import static org.mockito.ArgumentMatchers.any;
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.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;

import android.Manifest;
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.VirtualDeviceParams;
import android.content.Context;
@@ -41,24 +45,35 @@ 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.RemoteException;
import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.KeyEvent;

import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

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;

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

    private static final String DEVICE_NAME = "device name";
@@ -84,6 +99,11 @@ public class VirtualDeviceManagerServiceTest {
    private InputManagerInternal mInputManagerInternalMock;
    @Mock
    private IVirtualDeviceActivityListener mActivityListener;
    @Mock
    IPowerManager mIPowerManagerMock;
    @Mock
    IThermalService mIThermalServiceMock;
    private PowerManager mPowerManager;

    @Before
    public void setUp() {
@@ -102,10 +122,17 @@ public class VirtualDeviceManagerServiceTest {
        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);

        mInputController = new InputController(new Object(), mNativeWrapperMock);
        AssociationInfo associationInfo = new AssociationInfo(1, 0, null,
                MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
        mDeviceImpl = new VirtualDeviceImpl(mContext,
                /* association info */ null, new Binder(), /* uid */ 0, mInputController,
                (int associationId) -> {}, mPendingTrampolineCallback, mActivityListener,
                associationInfo, new Binder(), /* uid */ 0, mInputController,
                (int associationId) -> {
                }, mPendingTrampolineCallback, mActivityListener,
                new VirtualDeviceParams.Builder().build());
    }

@@ -117,6 +144,72 @@ public class VirtualDeviceManagerServiceTest {
        mDeviceImpl.onVirtualDisplayRemovedLocked(displayId);
    }

    @Test
    public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException {
        final int displayId = 2;
        mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
        verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
                nullable(String.class), nullable(String.class), nullable(WorkSource.class),
                nullable(String.class), anyInt());
        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(displayId));
    }

    @Test
    public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
            throws RemoteException {
        final int displayId = 2;
        mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
        assertThrows(IllegalStateException.class,
                () -> mDeviceImpl.onVirtualDisplayCreatedLocked(displayId));
        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(displayId));
    }

    @Test
    public void onVirtualDisplayRemovedLocked_unknownDisplayId_throwsException() {
        final int unknownDisplayId = 999;
        assertThrows(IllegalStateException.class,
                () -> mDeviceImpl.onVirtualDisplayRemovedLocked(unknownDisplayId));
    }

    @Test
    public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
        final int displayId = 2;
        mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
        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(displayId));

        IBinder wakeLock = wakeLockCaptor.getValue();
        mDeviceImpl.onVirtualDisplayRemovedLocked(displayId);
        verify(mIPowerManagerMock, Mockito.times(1)).releaseWakeLock(eq(wakeLock), anyInt());
    }

    @Test
    public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
        final int displayId = 2;
        mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
        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(displayId));
        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(