Merge "Keep VirtualDevice-created VirtualDisplays awake"

This commit is contained in:
Antony Sargent
2022-01-25 19:38:48 +00:00
committed by Android (Google) Code Review
2 changed files with 167 additions and 26 deletions

View File

@@ -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,22 +397,48 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
DisplayWindowPolicyController onVirtualDisplayCreatedLocked(int displayId) {
if (mVirtualDisplayIds.contains(displayId)) {
throw new IllegalStateException(
"Virtual device already have a virtual display with ID " + displayId);
synchronized (mVirtualDeviceLock) {
if (mVirtualDisplayIds.contains(displayId)) {
throw new IllegalStateException(
"Virtual device already have a virtual display with ID " + displayId);
}
mVirtualDisplayIds.add(displayId);
// 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,
getAllowedUserHandles(),
mParams.getAllowedActivities(),
mParams.getBlockedActivities(),
createListenerAdapter(displayId));
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);
}
mVirtualDisplayIds.add(displayId);
LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture(
displayId, false);
final GenericWindowPolicyController dwpc =
new GenericWindowPolicyController(FLAG_SECURE,
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
getAllowedUserHandles(),
mParams.getAllowedActivities(),
mParams.getBlockedActivities(),
createListenerAdapter(displayId));
mWindowPolicyControllers.put(displayId, dwpc);
return dwpc;
}
private ArraySet<UserHandle> getAllowedUserHandles() {
@@ -420,14 +460,22 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
void onVirtualDisplayRemovedLocked(int displayId) {
if (!mVirtualDisplayIds.contains(displayId)) {
throw new IllegalStateException(
"Virtual device doesn't have a virtual display with ID " + 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(
displayId, true);
mWindowPolicyControllers.remove(displayId);
}
mVirtualDisplayIds.remove(displayId);
LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture(
displayId, true);
mWindowPolicyControllers.remove(displayId);
}
int getOwnerUid() {

View File

@@ -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(