Merge "Add a flag for allowing always unlocked virtual displays"

This commit is contained in:
Antony Sargent
2021-12-01 17:12:48 +00:00
committed by Android (Google) Code Review
13 changed files with 243 additions and 1 deletions

View File

@@ -344,6 +344,16 @@ public final class DisplayManager {
*/
public static final int VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP = 1 << 11;
/**
* Virtual display flags: Indicates that the virtual display should always be unlocked and not
* have keyguard displayed on it. Only valid for virtual displays that aren't in the default
* display group.
*
* @see #createVirtualDisplay
* @see #VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP
* @hide
*/
public static final int VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED = 1 << 12;
/** @hide */
@IntDef(prefix = {"MATCH_CONTENT_FRAMERATE_"}, value = {
@@ -1363,5 +1373,13 @@ public final class DisplayManager {
* @hide
*/
String KEY_HIGH_REFRESH_RATE_BLACKLIST = "high_refresh_rate_blacklist";
/**
* Whether to allow the creation of always unlocked virtual displays by apps having the
* required permissions.
* @hide
*/
String KEY_ALLOW_ALWAYS_UNLOCKED_VIRTUAL_DISPLAYS =
"allow_always_unlocked_virtual_displays";
}
}

View File

@@ -298,6 +298,15 @@ public final class Display {
*/
public static final int FLAG_OWN_DISPLAY_GROUP = 1 << 8;
/**
* Flag: Indicates that the display should always be unlocked. Only valid on virtual displays
* that aren't in the default display group.
*
* @hide
* @see #getFlags()
*/
public static final int FLAG_ALWAYS_UNLOCKED = 1 << 9;
/**
* Display flag: Indicates that the contents of the display should not be scaled
* to fit the physical screen dimensions. Used for development only to emulate

View File

@@ -863,6 +863,9 @@ public final class DisplayInfo implements Parcelable {
if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) {
result.append(", FLAG_OWN_DISPLAY_GROUP");
}
if ((flags & Display.FLAG_ALWAYS_UNLOCKED) != 0) {
result.append(", FLAG_ALWAYS_UNLOCKED");
}
return result.toString();
}
}

View File

@@ -5882,6 +5882,10 @@
<permission android:name="android.permission.ADD_TRUSTED_DISPLAY"
android:protectionLevel="signature" />
<!-- Allows an application to create always-unlocked displays. @hide -->
<permission android:name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY"
android:protectionLevel="signature"/>
<!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. -->
<permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
android:protectionLevel="signature|role" />

View File

@@ -141,6 +141,15 @@ final class DisplayDeviceInfo {
*/
public static final int FLAG_OWN_DISPLAY_GROUP = 1 << 14;
/**
* Flag: Indicates that the display should always be unlocked. Only valid on virtual displays
* that aren't in the default display group.
* @see #FLAG_OWN_DISPLAY_GROUP
* @hide
*/
public static final int FLAG_ALWAYS_UNLOCKED = 1 << 15;
/**
* Touch attachment: Display does not receive touch.
*/

View File

@@ -16,11 +16,13 @@
package com.android.server.display;
import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT;
import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.hardware.display.DisplayManager.EventsMask;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
@@ -94,6 +96,8 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.provider.Settings;
import android.sysprop.DisplayProperties;
import android.text.TextUtils;
@@ -393,6 +397,7 @@ public final class DisplayManagerService extends SystemService {
private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
private final Injector mInjector;
private final DeviceConfigInterface mDeviceConfig;
// The minimum brightness curve, which guarantess that any brightness curve that dips below it
// is rejected by the system.
@@ -443,6 +448,7 @@ public final class DisplayManagerService extends SystemService {
DisplayManagerService(Context context, Injector injector) {
super(context);
mInjector = injector;
mDeviceConfig = mInjector.getDeviceConfig();
mContext = context;
mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
mUiHandler = UiThread.getHandler();
@@ -1254,6 +1260,31 @@ public final class DisplayManagerService extends SystemService {
}
}
if ((flags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0) {
if (callingUid != Process.SYSTEM_UID
&& !checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY,
"createVirtualDisplay()")) {
throw new SecurityException(
"Requires ADD_ALWAYS_UNLOCKED_DISPLAY permission to "
+ "create an always unlocked virtual display.");
}
boolean allowedByDeviceConfig = false;
final long token = Binder.clearCallingIdentity();
try {
allowedByDeviceConfig = mDeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
DisplayManager.DeviceConfig.KEY_ALLOW_ALWAYS_UNLOCKED_VIRTUAL_DISPLAYS,
false);
} finally {
Binder.restoreCallingIdentity(token);
}
if (!allowedByDeviceConfig) {
Slog.w(TAG, "Ignoring flag VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED "
+ "because it is not allowed by DeviceConfig");
flags &= ~VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
}
}
if ((flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == 0) {
flags &= ~VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
}
@@ -2366,6 +2397,11 @@ public final class DisplayManagerService extends SystemService {
return DisplayProperties
.debug_allow_non_native_refresh_rate_override().orElse(false);
}
@NonNull
public DeviceConfigInterface getDeviceConfig() {
return DeviceConfigInterface.REAL;
}
}
@VisibleForTesting

View File

@@ -378,6 +378,9 @@ final class LogicalDisplay {
if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) {
mBaseDisplayInfo.flags |= Display.FLAG_OWN_DISPLAY_GROUP;
}
if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED) != 0) {
mBaseDisplayInfo.flags |= Display.FLAG_ALWAYS_UNLOCKED;
}
Rect maskingInsets = getMaskingInsets(deviceInfo);
int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;

View File

@@ -16,6 +16,7 @@
package com.android.server.display;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
@@ -28,6 +29,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOUL
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
import static com.android.server.display.DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED;
import static com.android.server.display.DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED;
@@ -453,6 +455,10 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
mInfo.flags |= FLAG_TRUSTED;
}
if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0
&& (mInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) {
mInfo.flags |= FLAG_ALWAYS_UNLOCKED;
}
mInfo.type = Display.TYPE_VIRTUAL;
mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?

View File

@@ -5927,6 +5927,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
.getKeyguardController().isKeyguardGoingAway(mDisplayId);
}
/**
* @return whether keyguard should always be unlocked for this display
*/
boolean isKeyguardAlwaysUnlocked() {
return (mDisplayInfo.flags & Display.FLAG_ALWAYS_UNLOCKED) != 0;
}
/**
* @return whether AOD is showing on this display
*/

View File

@@ -157,6 +157,11 @@ class KeyguardController {
* Update the Keyguard showing state.
*/
void setKeyguardShown(int displayId, boolean keyguardShowing, boolean aodShowing) {
if (mRootWindowContainer.getDisplayContent(displayId).isKeyguardAlwaysUnlocked()) {
Slog.i(TAG, "setKeyguardShown ignoring always unlocked display " + displayId);
return;
}
final KeyguardDisplayState state = getDisplayState(displayId);
final boolean aodChanged = aodShowing != state.mAodShowing;
// If keyguard is going away, but SystemUI aborted the transition, need to reset state.

View File

@@ -16,20 +16,27 @@
package com.android.server.display;
import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.PropertyInvalidatedCache;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.graphics.Insets;
import android.graphics.Rect;
import android.hardware.display.BrightnessConfiguration;
@@ -48,6 +55,8 @@ import android.os.IBinder;
import android.os.MessageQueue;
import android.os.Process;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
@@ -55,7 +64,9 @@ import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceControl;
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -65,6 +76,7 @@ import com.android.server.SystemService;
import com.android.server.display.DisplayManagerService.SyncRoot;
import com.android.server.lights.LightsManager;
import com.android.server.sensors.SensorManagerInternal;
import com.android.server.testutils.FakeDeviceConfigInterface;
import com.android.server.wm.WindowManagerInternal;
import com.google.common.collect.ImmutableMap;
@@ -105,6 +117,7 @@ public class DisplayManagerServiceTest {
public TestRule compatChangeRule = new PlatformCompatChangeRule();
private Context mContext;
private FakeDeviceConfigInterface mDeviceConfig;
private final DisplayManagerService.Injector mShortMockedInjector =
new DisplayManagerService.Injector() {
@@ -127,6 +140,12 @@ public class DisplayManagerServiceTest {
return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
(String name, boolean secure) -> mMockDisplayToken);
}
@NonNull
@Override
public DeviceConfigInterface getDeviceConfig() {
return mDeviceConfig;
}
}
private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
@@ -169,7 +188,8 @@ public class DisplayManagerServiceTest {
LocalServices.removeServiceForTest(SensorManagerInternal.class);
LocalServices.addService(SensorManagerInternal.class, mMockSensorManagerInternal);
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mDeviceConfig = new FakeDeviceConfigInterface();
// Disable binder caches in this process.
PropertyInvalidatedCache.disableForTestMode();
@@ -618,6 +638,25 @@ public class DisplayManagerServiceTest {
assertEquals(displayManager.getVirtualDisplaySurfaceInternal(mMockAppToken), surface);
}
/**
* Tests that specifying the VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED flag is processed correctly
* when it is allowed by DeviceConfig.
*/
@Test
public void testCreateVirtualDisplay_alwaysUnlockedAllowed() {
testCreateVirtualDisplay_alwaysUnlocked(/*deviceConfigAllows*/ true, /*flagExpected*/ true);
}
/**
* Tests that specifying the VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED flag when DeviceConfig does
* not allow it results in the flag being stripped from the final flags.
*/
@Test
public void testCreateVirtualDisplay_alwaysUnlockedDisallowed() {
testCreateVirtualDisplay_alwaysUnlocked(
/*deviceConfigAllows*/ false, /*flagExpected*/ false);
}
/**
* Tests that there is a display change notification if the frame rate override
* list is updated.
@@ -1042,6 +1081,45 @@ public class DisplayManagerServiceTest {
assertEquals(expectedRefreshRate, displayInfo.getRefreshRate(), 0.01f);
}
private void testCreateVirtualDisplay_alwaysUnlocked(boolean deviceConfigAllows,
boolean flagExpected) {
mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
DisplayManager.DeviceConfig.KEY_ALLOW_ALWAYS_UNLOCKED_VIRTUAL_DISPLAYS,
deviceConfigAllows ? "true" : "false", /*makeDefault*/ false);
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mBasicInjector);
registerDefaultDisplays(displayManager);
String uniqueId = "uniqueId --- ALWAYS_UNLOCKED";
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP
| DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
DisplayManagerService.BinderService bs = displayManager.new BinderService();
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY)).thenReturn(
PackageManager.PERMISSION_GRANTED);
when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
PackageManager.PERMISSION_GRANTED);
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
VIRTUAL_DISPLAY_NAME, 600, 800, 320);
builder.setFlags(flags);
builder.setUniqueId(uniqueId);
int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
null /* projection */, PACKAGE_NAME);
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
assertNotNull(ddi);
if (flagExpected) {
assertNotEquals(ddi.flags & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED, 0);
} else {
assertEquals(ddi.flags & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED, 0);
}
}
private int getDisplayIdForDisplayDevice(
DisplayManagerService displayManager,
DisplayManagerService.BinderService displayManagerBinderService,

View File

@@ -35,6 +35,7 @@ import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -63,6 +64,8 @@ import android.os.LocaleList;
import android.os.PowerManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.IDisplayWindowListener;
import androidx.test.filters.MediumTest;
@@ -220,6 +223,66 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
assertEquals(1, removed.size());
}
@Test
public void testSetLockScreenShownWithVirtualDisplay() {
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
displayInfo.type = Display.TYPE_VIRTUAL;
DisplayContent virtualDisplay = createNewDisplay(displayInfo);
// Make sure we're starting out with 2 unlocked displays
assertEquals(2, mRootWindowContainer.getChildCount());
mRootWindowContainer.forAllDisplays(displayContent -> {
assertFalse(displayContent.isKeyguardLocked());
assertFalse(displayContent.isAodShowing());
});
// Check that setLockScreenShown locks both displays
mAtm.setLockScreenShown(true, true);
mRootWindowContainer.forAllDisplays(displayContent -> {
assertTrue(displayContent.isKeyguardLocked());
assertTrue(displayContent.isAodShowing());
});
// Check setLockScreenShown unlocking both displays
mAtm.setLockScreenShown(false, false);
mRootWindowContainer.forAllDisplays(displayContent -> {
assertFalse(displayContent.isKeyguardLocked());
assertFalse(displayContent.isAodShowing());
});
}
@Test
public void testSetLockScreenShownWithAlwaysUnlockedVirtualDisplay() {
assertEquals(Display.DEFAULT_DISPLAY, mRootWindowContainer.getChildAt(0).getDisplayId());
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
displayInfo.type = Display.TYPE_VIRTUAL;
displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
displayInfo.flags = Display.FLAG_OWN_DISPLAY_GROUP | Display.FLAG_ALWAYS_UNLOCKED;
DisplayContent newDisplay = createNewDisplay(displayInfo);
// Make sure we're starting out with 2 unlocked displays
assertEquals(2, mRootWindowContainer.getChildCount());
mRootWindowContainer.forAllDisplays(displayContent -> {
assertFalse(displayContent.isKeyguardLocked());
assertFalse(displayContent.isAodShowing());
});
// setLockScreenShown should only lock the default display, not the virtual one
mAtm.setLockScreenShown(true, true);
assertTrue(mDefaultDisplay.isKeyguardLocked());
assertTrue(mDefaultDisplay.isAodShowing());
DisplayContent virtualDisplay = mRootWindowContainer.getDisplayContent(
newDisplay.getDisplayId());
assertNotEquals(Display.DEFAULT_DISPLAY, virtualDisplay.getDisplayId());
assertFalse(virtualDisplay.isKeyguardLocked());
assertFalse(virtualDisplay.isAodShowing());
}
/*
a test to verify b/144045134 - ignore PIP mode request for destroyed activity.
mocks r.getParent() to return null to cause NPE inside enterPipRunnable#run() in

View File

@@ -266,6 +266,7 @@ public class SystemServicesTestRule implements TestRule {
doNothing().when(amInternal).updateOomLevelsForDisplay(anyInt());
doNothing().when(amInternal).broadcastGlobalConfigurationChanged(anyInt(), anyBoolean());
doNothing().when(amInternal).cleanUpServices(anyInt(), any(), any());
doNothing().when(amInternal).reportCurKeyguardUsageEvent(anyBoolean());
doReturn(UserHandle.USER_SYSTEM).when(amInternal).getCurrentUserId();
doReturn(TEST_USER_PROFILE_IDS).when(amInternal).getCurrentProfileIds();
doReturn(true).when(amInternal).isUserRunning(anyInt(), anyInt());