Merge "Keep VirtualDevice-created VirtualDisplays awake"
This commit is contained in:
committed by
Android (Google) Code Review
commit
2557acb57e
@@ -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() {
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user