Swap default display when lid-switch is toggled.

For devices with multiple internal displays that support
a lid-switch, automatically swap which DisplayDevice is
associated with the default Logical Display.

Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/display
Bug: 130724654
Change-Id: I7280b4f49c0f11df4ecd0f2a1547c95c168265c7
This commit is contained in:
Santos Cordon
2020-10-07 14:10:13 +01:00
parent b943579de0
commit b254db1866
10 changed files with 281 additions and 14 deletions

View File

@@ -266,6 +266,12 @@ public abstract class DisplayManagerInternal {
*/
public abstract void ignoreProximitySensorUntilChanged();
/**
* Sets the folded state of the device.
* TODO: b/168208522 - Remove in favor of DisplayStatePolicy when that is available.
*/
public abstract void setDeviceFolded(boolean isFolded);
/**
* Describes the requested power state of the display.
*

View File

@@ -4443,4 +4443,13 @@
<!-- WindowsManager JetPack display features -->
<string name="config_display_features" translatable="false" />
<!-- Physical Display IDs of the display-devices that are swapped when a folding device folds.
This list is expected to contain two elements: the first is the display to use
when the device is folded, the second is the display to use when unfolded. If the array
is empty or the display IDs are not recognized, this feature is turned off and the value
ignored.
TODO: b/170470621 - remove once we can have multiple Internal displays in DMS as
well as a notification from DisplayStateManager. -->
<string-array name="config_internalFoldedPhysicalDisplayIds" translatable="false" />
</resources>

View File

@@ -4076,4 +4076,5 @@
<java-symbol type="array" name="config_keep_warming_services" />
<java-symbol type="string" name="config_display_features" />
<java-symbol type="array" name="config_internalFoldedPhysicalDisplayIds" />
</resources>

View File

@@ -112,6 +112,17 @@ class DisplayDeviceRepository implements DisplayAdapter.Listener {
}
}
public DisplayDevice getByIdLocked(@NonNull String uniqueId) {
final int count = mDisplayDevices.size();
for (int i = 0; i < count; i++) {
final DisplayDevice d = mDisplayDevices.get(i);
if (uniqueId.equals(d.getUniqueId())) {
return d;
}
}
return null;
}
private void handleDisplayDeviceAdded(DisplayDevice device) {
synchronized (mSyncRoot) {
DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();

View File

@@ -324,7 +324,7 @@ public final class DisplayManagerService extends SystemService {
mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo,
mLogicalDisplayMapper = new LogicalDisplayMapper(context, mDisplayDeviceRepo,
new LogicalDisplayListener(), mPersistentDataStore);
mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
Resources resources = mContext.getResources();
@@ -576,6 +576,7 @@ public final class DisplayManagerService extends SystemService {
Trace.traceBegin(Trace.TRACE_TAG_POWER, "requestGlobalDisplayState("
+ Display.stateToString(state)
+ ", brightness=" + brightnessState + ")");
mGlobalDisplayState = state;
mGlobalDisplayBrightness = brightnessState;
applyGlobalDisplayStateLocked(mTempDisplayStateWorkQueue);
@@ -983,6 +984,15 @@ public final class DisplayManagerService extends SystemService {
scheduleTraversalLocked(false);
}
private void handleLogicalDisplaySwappedLocked(@NonNull LogicalDisplay display) {
final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
final Runnable work = updateDisplayStateLocked(device);
if (work != null) {
mHandler.post(work);
}
handleLogicalDisplayChangedLocked(display);
}
private void applyGlobalDisplayStateLocked(List<Runnable> workQueue) {
mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
Runnable runnable = updateDisplayStateLocked(device);
@@ -997,10 +1007,15 @@ public final class DisplayManagerService extends SystemService {
// by the display power controller (if known).
DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
// TODO - multi-display - The rules regarding what display state to apply to each
// TODO - b/170498827 The rules regarding what display state to apply to each
// display will depend on the configuration/mapping of logical displays.
return device.requestDisplayStateLocked(
mGlobalDisplayState, mGlobalDisplayBrightness);
// Clean up LogicalDisplay.isEnabled() mechanism once this is fixed.
int state = mGlobalDisplayState;
final LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
if (display != null && !display.isEnabled()) {
state = Display.STATE_OFF;
}
return device.requestDisplayStateLocked(state, mGlobalDisplayBrightness);
}
return null;
}
@@ -1346,6 +1361,12 @@ public final class DisplayManagerService extends SystemService {
}
}
void setFoldOverride(Boolean isFolded) {
synchronized (mSyncRoot) {
mLogicalDisplayMapper.setFoldOverrideLocked(isFolded);
}
}
private void clearViewportsLocked() {
mViewports.clear();
}
@@ -1698,6 +1719,10 @@ public final class DisplayManagerService extends SystemService {
case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED:
handleLogicalDisplayRemovedLocked(display);
break;
case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_SWAPPED:
handleLogicalDisplaySwappedLocked(display);
break;
}
}
@@ -2538,6 +2563,13 @@ public final class DisplayManagerService extends SystemService {
public void ignoreProximitySensorUntilChanged() {
mDisplayPowerController.ignoreProximitySensorUntilChanged();
}
@Override
public void setDeviceFolded(boolean isFolded) {
synchronized (mSyncRoot) {
mLogicalDisplayMapper.setDeviceFoldedLocked(isFolded);
}
}
}
class DesiredDisplayModeSpecsObserver

View File

@@ -56,6 +56,8 @@ class DisplayManagerShellCommand extends ShellCommand {
return setDisplayWhiteBalanceLoggingEnabled(false);
case "dwb-set-cct":
return setAmbientColorTemperatureOverride();
case "set-fold":
return setFoldOverride();
default:
return handleDefaultCommands(cmd);
}
@@ -82,6 +84,8 @@ class DisplayManagerShellCommand extends ShellCommand {
pw.println(" Disable display white-balance logging.");
pw.println(" dwb-set-cct CCT");
pw.println(" Sets the ambient color temperature override to CCT (use -1 to disable).");
pw.println(" set-fold [fold|unfold|reset]");
pw.println(" Simulates the 'fold' state of a device. 'reset' for default behavior.");
pw.println();
Intent.printIntentArgsHelp(pw , "");
}
@@ -148,4 +152,26 @@ class DisplayManagerShellCommand extends ShellCommand {
mService.setAmbientColorTemperatureOverride(cct);
return 0;
}
private int setFoldOverride() {
String state = getNextArg();
if (state == null) {
getErrPrintWriter().println("Error: no parameter specified for set-fold");
return 1;
}
final Boolean isFolded;
if ("fold".equals(state)) {
isFolded = true;
} else if ("unfold".equals(state)) {
isFolded = false;
} else if ("reset".equals(state)) {
isFolded = null;
} else {
getErrPrintWriter().println("Error: Invalid fold state request: " + state);
return 1;
}
mService.setFoldOverride(isFolded);
return 0;
}
}

View File

@@ -16,9 +16,11 @@
package com.android.server.display;
import android.annotation.NonNull;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerInternal;
import android.util.Slog;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -57,6 +59,7 @@ import java.util.Objects;
* </p>
*/
final class LogicalDisplay {
private static final String TAG = "LogicalDisplay";
private final DisplayInfo mBaseDisplayInfo = new DisplayInfo();
// The layer stack we use when the display has been blanked to prevent any
@@ -114,6 +117,12 @@ final class LogicalDisplay {
private final Rect mTempLayerStackRect = new Rect();
private final Rect mTempDisplayRect = new Rect();
/**
* Indicates that the Logical display is enabled (default). See {@link #setEnabled} for
* more information.
*/
private boolean mIsEnabled = true;
public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
mDisplayId = displayId;
mLayerStack = layerStack;
@@ -575,6 +584,44 @@ final class LogicalDisplay {
mDisplayScalingDisabled = disableScaling;
}
/**
* Swap the underlying {@link DisplayDevice} with the specificed LogicalDisplay.
*
* @param targetDisplay The display with which to swap display-devices.
* @return {@code true} if the displays were swapped, {@code false} otherwise.
*/
public boolean swapDisplaysLocked(@NonNull LogicalDisplay targetDisplay) {
final DisplayDevice targetDevice = targetDisplay.getPrimaryDisplayDeviceLocked();
if (mPrimaryDisplayDevice == null || targetDevice == null) {
Slog.e(TAG, "Missing display device during swap: " + mPrimaryDisplayDevice + " , "
+ targetDevice);
return false;
}
final DisplayDevice tmpDevice = mPrimaryDisplayDevice;
mPrimaryDisplayDevice = targetDisplay.mPrimaryDisplayDevice;
targetDisplay.mPrimaryDisplayDevice = tmpDevice;
return true;
}
/**
* Sets the LogicalDisplay to be enabled or disabled. If the display is not enabled,
* the system will always set the display to power off, regardless of the global state of the
* device.
* TODO: b/170498827 - Remove when updateDisplayStateLocked is updated.
*/
public void setEnabled(boolean isEnabled) {
mIsEnabled = isEnabled;
}
/**
* @return {@code true} iff the LogicalDisplay is enabled or {@code false}
* if disabled indicating that the display has been forced to be OFF.
*/
public boolean isEnabled() {
return mIsEnabled;
}
public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId);
pw.println("mLayerStack=" + mLayerStack);

View File

@@ -16,7 +16,9 @@
package com.android.server.display;
import android.content.Context;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -26,6 +28,7 @@ import com.android.internal.util.IndentingPrintWriter;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -39,9 +42,12 @@ import java.util.function.Consumer;
class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private static final String TAG = "LogicalDisplayMapper";
private static final boolean DEBUG = false;
public static final int LOGICAL_DISPLAY_EVENT_ADDED = 1;
public static final int LOGICAL_DISPLAY_EVENT_CHANGED = 2;
public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 3;
public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4;
/**
* Temporary display info, used for comparing display configurations.
@@ -58,6 +64,24 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
*/
private final boolean mSingleDisplayDemoMode;
/**
* Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
* when {@link mIsFolded} is set to {@code true}.
*/
private String mDisplayIdToUseWhenFolded;
/**
* Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
* when {@link mIsFolded} is set to {@code false}.
*/
private String mDisplayIdToUseWhenUnfolded;
/** Overrides the folded state of the device. For use with ADB commands. */
private Boolean mIsFoldedOverride;
/** Saves the last device fold state. */
private boolean mIsFolded;
/**
* List of all logical displays indexed by logical display id.
* Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
@@ -71,13 +95,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private final PersistentDataStore mPersistentDataStore;
private final Listener mListener;
LogicalDisplayMapper(DisplayDeviceRepository repo, Listener listener,
LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener,
PersistentDataStore persistentDataStore) {
mDisplayDeviceRepo = repo;
mPersistentDataStore = persistentDataStore;
mListener = listener;
mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
mDisplayDeviceRepo.addListener(this);
loadFoldedDisplayConfig(context);
}
@Override
@@ -141,23 +167,104 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
}
public void setDeviceFoldedLocked(boolean isFolded) {
mIsFolded = isFolded;
if (mIsFoldedOverride != null) {
isFolded = mIsFoldedOverride.booleanValue();
}
if (mDisplayIdToUseWhenFolded == null || mDisplayIdToUseWhenUnfolded == null
|| mLogicalDisplays.size() < 2) {
// Do nothing if this behavior is disabled or there are less than two displays.
return;
}
final DisplayDevice deviceFolded =
mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenFolded);
final DisplayDevice deviceUnfolded =
mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenUnfolded);
if (deviceFolded == null || deviceUnfolded == null) {
// If the expected devices for folding functionality are not present, return early.
return;
}
// Find the associated LogicalDisplays for the configured "folding" DeviceDisplays.
final LogicalDisplay displayFolded = getLocked(deviceFolded);
final LogicalDisplay displayUnfolded = getLocked(deviceUnfolded);
if (displayFolded == null || displayFolded == null) {
// If the expected displays are not present, return early.
return;
}
// Find out which display is currently default and which is disabled.
final LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
final LogicalDisplay disabledDisplay;
if (defaultDisplay == displayFolded) {
disabledDisplay = displayUnfolded;
} else if (defaultDisplay == displayUnfolded) {
disabledDisplay = displayFolded;
} else {
// If neither folded or unfolded displays are currently set to the default display, we
// are in an unknown state and it's best to log the error and bail.
Slog.e(TAG, "Unexpected: when attempting to swap displays, neither of the two"
+ " configured displays were set up as the default display. Default: "
+ defaultDisplay.getDisplayInfoLocked() + ", ConfiguredDisplays: [ folded="
+ displayFolded.getDisplayInfoLocked() + ", unfolded="
+ displayUnfolded.getDisplayInfoLocked() + " ]");
return;
}
if (isFolded == (defaultDisplay == displayFolded)) {
// Nothing to do, already in the right state.
return;
}
// Everything was checked and we need to swap, lets swap.
displayFolded.swapDisplaysLocked(displayUnfolded);
// We ensure that the non-default Display is always forced to be off. This was likely
// already done in a previous iteration, but we do it with each swap in case something in
// the underlying LogicalDisplays changed: like LogicalDisplay recreation, for example.
defaultDisplay.setEnabled(true);
disabledDisplay.setEnabled(false);
// Update the world
updateLogicalDisplaysLocked();
if (DEBUG) {
Slog.d(TAG, "Folded displays: isFolded: " + isFolded + ", defaultDisplay? "
+ defaultDisplay.getDisplayInfoLocked());
}
}
public void dumpLocked(PrintWriter pw) {
pw.println("LogicalDisplayMapper:");
pw.println(" mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
pw.println(" mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.increaseIndent();
ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
ipw.println("mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
final int logicalDisplayCount = mLogicalDisplays.size();
pw.println();
pw.println(" Logical Displays: size=" + logicalDisplayCount);
ipw.println();
ipw.println("Logical Displays: size=" + logicalDisplayCount);
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.increaseIndent();
for (int i = 0; i < logicalDisplayCount; i++) {
int displayId = mLogicalDisplays.keyAt(i);
LogicalDisplay display = mLogicalDisplays.valueAt(i);
pw.println(" Display " + displayId + ":");
ipw.println("Display " + displayId + ":");
ipw.increaseIndent();
display.dumpLocked(ipw);
ipw.decreaseIndent();
ipw.println();
}
}
void setFoldOverrideLocked(Boolean isFolded) {
if (!Objects.equals(isFolded, mIsFoldedOverride)) {
mIsFoldedOverride = isFolded;
setDeviceFoldedLocked(mIsFolded);
}
}
@@ -211,8 +318,11 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
mListener.onLogicalDisplayEventLocked(display,
LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED);
} else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
mListener.onLogicalDisplayEventLocked(display,
LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED);
final String oldUniqueId = mTempDisplayInfo.uniqueId;
final String newUniqueId = display.getDisplayInfoLocked().uniqueId;
final int eventMsg = TextUtils.equals(oldUniqueId, newUniqueId)
? LOGICAL_DISPLAY_EVENT_CHANGED : LOGICAL_DISPLAY_EVENT_SWAPPED;
mListener.onLogicalDisplayEventLocked(display, eventMsg);
} else {
// While applications shouldn't know nor care about the non-overridden info, we
// still need to let WindowManager know so it can update its own internal state for
@@ -236,6 +346,21 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
return displayId;
}
private void loadFoldedDisplayConfig(Context context) {
final String[] displayIds = context.getResources().getStringArray(
com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
if (displayIds.length != 2 || TextUtils.isEmpty(displayIds[0])
|| TextUtils.isEmpty(displayIds[1])) {
Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(displayIds)
+ "]");
return;
}
mDisplayIdToUseWhenFolded = displayIds[0];
mDisplayIdToUseWhenUnfolded = displayIds[1];
}
public interface Listener {
void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
void onTraversalRequested();

View File

@@ -80,6 +80,8 @@ class DisplayFoldController {
}
void setDeviceFolded(boolean folded) {
mDisplayManagerInternal.setDeviceFolded(folded);
if (mFolded != null && mFolded == folded) {
return;
}

View File

@@ -117,6 +117,7 @@ import android.database.ContentObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.hdmi.HdmiAudioSystemClient;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPlaybackClient;
@@ -366,6 +367,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
StatusBarManagerInternal mStatusBarManagerInternal;
AudioManagerInternal mAudioManagerInternal;
DisplayManager mDisplayManager;
DisplayManagerInternal mDisplayManagerInternal;
boolean mPreloadedRecentApps;
final Object mServiceAquireLock = new Object();
Vibrator mVibrator; // Vibrator for giving feedback of orientation changes
@@ -469,6 +471,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int mLidKeyboardAccessibility;
int mLidNavigationAccessibility;
private boolean mLidControlsDisplayFold;
private boolean mShouldSwapDisplaysOnLidSwitch;
int mShortPressOnPowerBehavior;
int mLongPressOnPowerBehavior;
int mVeryLongPressOnPowerBehavior;
@@ -1752,6 +1755,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mPackageManager = mContext.getPackageManager();
mHasFeatureWatch = mPackageManager.hasSystemFeature(FEATURE_WATCH);
mHasFeatureLeanback = mPackageManager.hasSystemFeature(FEATURE_LEANBACK);
@@ -1845,6 +1849,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
com.android.internal.R.integer.config_lidNavigationAccessibility);
mLidControlsDisplayFold = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_lidControlsDisplayFold);
mShouldSwapDisplaysOnLidSwitch = mContext.getResources().getStringArray(
com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds).length == 2;
mAllowTheaterModeWakeFromKey = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromKey);
@@ -5035,6 +5041,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
final int lidState = mDefaultDisplayPolicy.getLidState();
if (mLidControlsDisplayFold && mDisplayFoldController != null) {
mDisplayFoldController.requestDeviceFolded(lidState == LID_CLOSED);
} else if (mShouldSwapDisplaysOnLidSwitch) {
mDisplayManagerInternal.setDeviceFolded(lidState == LID_CLOSED);
} else if (lidState == LID_CLOSED) {
int lidBehavior = getLidBehavior();
switch (lidBehavior) {