Merge "Turn OFF any changing displays during device-state transitions" into sc-dev

This commit is contained in:
Santos Cordon
2021-04-26 15:54:00 +00:00
committed by Android (Google) Code Review
8 changed files with 367 additions and 77 deletions

View File

@@ -657,6 +657,12 @@
<!-- Indicate the display area rect for foldable devices in folded state. --> <!-- Indicate the display area rect for foldable devices in folded state. -->
<string name="config_foldedArea"></string> <string name="config_foldedArea"></string>
<!-- Indicates that the device supports having more than one internal display on at the same
time. Only applicable to devices with more than one internal display. If this option is
set to false, DisplayManager will make additional effort to ensure no more than 1 internal
display is powered on at the same time. -->
<bool name="config_supportsConcurrentInternalDisplays">true</bool>
<!-- Desk dock behavior --> <!-- Desk dock behavior -->
<!-- The number of degrees to rotate the display when the device is in a desk dock. <!-- The number of degrees to rotate the display when the device is in a desk dock.

View File

@@ -3800,6 +3800,7 @@
<!-- For Foldables --> <!-- For Foldables -->
<java-symbol type="array" name="config_foldedDeviceStates" /> <java-symbol type="array" name="config_foldedDeviceStates" />
<java-symbol type="string" name="config_foldedArea" /> <java-symbol type="string" name="config_foldedArea" />
<java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" />
<java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" /> <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" />
<java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" /> <java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" />

View File

@@ -431,8 +431,8 @@ public final class DisplayManagerService extends SystemService {
mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper()); mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
mUiHandler = UiThread.getHandler(); mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore); mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo, mLogicalDisplayMapper = new LogicalDisplayMapper(mContext, mDisplayDeviceRepo,
new LogicalDisplayListener()); new LogicalDisplayListener(), mSyncRoot, mHandler);
mDisplayModeDirector = new DisplayModeDirector(context, mHandler); mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
mBrightnessSynchronizer = new BrightnessSynchronizer(mContext); mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
Resources resources = mContext.getResources(); Resources resources = mContext.getResources();
@@ -1268,7 +1268,7 @@ public final class DisplayManagerService extends SystemService {
DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) { if (dpc != null) {
dpc.onDisplayChangedLocked(); dpc.onDisplayChanged();
} }
} }
@@ -1304,12 +1304,23 @@ public final class DisplayManagerService extends SystemService {
handleLogicalDisplayChangedLocked(display); handleLogicalDisplayChangedLocked(display);
} }
private void handleLogicalDisplayDeviceStateTransitionLocked(@NonNull LogicalDisplay display) {
final int displayId = display.getDisplayIdLocked();
final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
dpc.onDeviceStateTransition();
}
}
private Runnable updateDisplayStateLocked(DisplayDevice device) { private Runnable updateDisplayStateLocked(DisplayDevice device) {
// Blank or unblank the display immediately to match the state requested // Blank or unblank the display immediately to match the state requested
// by the display power controller (if known). // by the display power controller (if known).
DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) { if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
if (display == null) {
return null;
}
final int displayId = display.getDisplayIdLocked(); final int displayId = display.getDisplayIdLocked();
final int state = mDisplayStates.get(displayId); final int state = mDisplayStates.get(displayId);
@@ -1453,9 +1464,12 @@ public final class DisplayManagerService extends SystemService {
clearViewportsLocked(); clearViewportsLocked();
// Configure each display device. // Configure each display device.
mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> { mLogicalDisplayMapper.forEachLocked((LogicalDisplay display) -> {
configureDisplayLocked(t, device); final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
device.performTraversalLocked(t); if (device != null) {
configureDisplayLocked(t, device);
device.performTraversalLocked(t);
}
}); });
// Tell the input system about these new viewports. // Tell the input system about these new viewports.
@@ -2159,6 +2173,10 @@ public final class DisplayManagerService extends SystemService {
case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED: case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED:
handleLogicalDisplayFrameRateOverridesChangedLocked(display); handleLogicalDisplayFrameRateOverridesChangedLocked(display);
break; break;
case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION:
handleLogicalDisplayDeviceStateTransitionLocked(display);
break;
} }
} }

View File

@@ -780,15 +780,23 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
* when displays get swapped on foldable devices. For example, different brightness properties * when displays get swapped on foldable devices. For example, different brightness properties
* of each display need to be properly reflected in AutomaticBrightnessController. * of each display need to be properly reflected in AutomaticBrightnessController.
*/ */
public void onDisplayChangedLocked() { public void onDisplayChanged() {
// TODO: b/175821789 - Support high brightness on multiple (folding) displays // TODO: b/175821789 - Support high brightness on multiple (folding) displays
mUniqueDisplayId = mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); mUniqueDisplayId = mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
mDisplayDeviceConfig = mLogicalDisplay.getPrimaryDisplayDeviceLocked() mDisplayDeviceConfig = mLogicalDisplay.getPrimaryDisplayDeviceLocked()
.getDisplayDeviceConfig(); .getDisplayDeviceConfig();
loadAmbientLightSensor(); loadAmbientLightSensor();
} }
/**
* Called when the displays are preparing to transition from one device state to another.
* This process involves turning off some displays so we need updatePowerState() to run and
* calculate the new state.
*/
public void onDeviceStateTransition() {
sendUpdatePowerState();
}
/** /**
* Unregisters all listeners and interrupts all running threads; halting future work. * Unregisters all listeners and interrupts all running threads; halting future work.
* *
@@ -1024,7 +1032,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mIgnoreProximityUntilChanged = false; mIgnoreProximityUntilChanged = false;
} }
if (!mLogicalDisplay.isEnabled() || mScreenOffBecauseOfProximity) { if (!mLogicalDisplay.isEnabled()
|| mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION
|| mScreenOffBecauseOfProximity) {
state = Display.STATE_OFF; state = Display.STATE_OFF;
} }

View File

@@ -16,6 +16,7 @@
package com.android.server.display; package com.android.server.display;
import android.annotation.IntDef;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.graphics.Point; import android.graphics.Point;
@@ -65,6 +66,33 @@ import java.util.Objects;
final class LogicalDisplay { final class LogicalDisplay {
private static final String TAG = "LogicalDisplay"; private static final String TAG = "LogicalDisplay";
/**
* Phase indicating the logical display's existence is hidden from the rest of the framework.
* This can happen if the current layout has specifically requested to keep this display
* disabled.
*/
static final int DISPLAY_PHASE_DISABLED = -1;
/**
* Phase indicating that the logical display is going through a layout transition.
* When in this phase, other systems can choose to special case power-state handling of a
* display that might be in a transition.
*/
static final int DISPLAY_PHASE_LAYOUT_TRANSITION = 0;
/**
* The display is exposed to the rest of the system and its power state is determined by a
* power-request from PowerManager.
*/
static final int DISPLAY_PHASE_ENABLED = 1;
@IntDef(prefix = {"DISPLAY_PHASE" }, value = {
DISPLAY_PHASE_DISABLED,
DISPLAY_PHASE_LAYOUT_TRANSITION,
DISPLAY_PHASE_ENABLED
})
@interface DisplayPhase {}
// The layer stack we use when the display has been blanked to prevent any // The layer stack we use when the display has been blanked to prevent any
// of its content from appearing. // of its content from appearing.
private static final int BLANK_LAYER_STACK = -1; private static final int BLANK_LAYER_STACK = -1;
@@ -129,10 +157,12 @@ final class LogicalDisplay {
private final Rect mTempDisplayRect = new Rect(); private final Rect mTempDisplayRect = new Rect();
/** /**
* Indicates that the Logical display is enabled (default). See {@link #setEnabled} for * Indicates the current phase of the display. Generally, phases supersede any
* more information. * requests from PowerManager in DPC's calculation for the display state. Only when the
* phase is ENABLED does PowerManager's request for the display take effect.
*/ */
private boolean mIsEnabled = true; @DisplayPhase
private int mPhase = DISPLAY_PHASE_ENABLED;
/** /**
* The UID mappings for refresh rate override * The UID mappings for refresh rate override
@@ -721,27 +751,32 @@ final class LogicalDisplay {
return old; return old;
} }
/** public void setPhase(@DisplayPhase int phase) {
* Sets the LogicalDisplay to be enabled or disabled. If the display is not enabled, mPhase = phase;
* 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} * Returns the currently set phase for this LogicalDisplay. Phases are used when transitioning
* if disabled indicating that the display has been forced to be OFF. * from one device state to another. {@see LogicalDisplayMapper}.
*/
@DisplayPhase
public int getPhase() {
return mPhase;
}
/**
* @return {@code true} if the LogicalDisplay is enabled or {@code false}
* if disabled indicating that the display should be hidden from the rest of the apps and
* framework.
*/ */
public boolean isEnabled() { public boolean isEnabled() {
return mIsEnabled; // DISPLAY_PHASE_LAYOUT_TRANSITION is still considered an 'enabled' phase.
return mPhase == DISPLAY_PHASE_ENABLED || mPhase == DISPLAY_PHASE_LAYOUT_TRANSITION;
} }
public void dumpLocked(PrintWriter pw) { public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId); pw.println("mDisplayId=" + mDisplayId);
pw.println("mIsEnabled=" + mIsEnabled); pw.println("mPhase=" + mPhase);
pw.println("mLayerStack=" + mLayerStack); pw.println("mLayerStack=" + mLayerStack);
pw.println("mHasContent=" + mHasContent); pw.println("mHasContent=" + mHasContent);
pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}"); pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");

View File

@@ -16,18 +16,24 @@
package com.android.server.display; package com.android.server.display;
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemProperties; import android.os.SystemProperties;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter;
import android.util.Slog; import android.util.Slog;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import android.view.Display; import android.view.Display;
import android.view.DisplayAddress; import android.view.DisplayAddress;
import android.view.DisplayInfo; import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.LogicalDisplay.DisplayPhase;
import com.android.server.display.layout.Layout; import com.android.server.display.layout.Layout;
import java.io.PrintWriter; import java.io.PrintWriter;
@@ -55,11 +61,20 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 3; public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 3;
public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4; public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4;
public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5; public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5;
public static final int LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION = 6;
public static final int DISPLAY_GROUP_EVENT_ADDED = 1; public static final int DISPLAY_GROUP_EVENT_ADDED = 1;
public static final int DISPLAY_GROUP_EVENT_CHANGED = 2; public static final int DISPLAY_GROUP_EVENT_CHANGED = 2;
public static final int DISPLAY_GROUP_EVENT_REMOVED = 3; public static final int DISPLAY_GROUP_EVENT_REMOVED = 3;
private static final int TIMEOUT_STATE_TRANSITION_MILLIS = 500;
private static final int MSG_TRANSITION_TO_PENDING_DEVICE_STATE = 1;
private static final int UPDATE_STATE_NEW = 0;
private static final int UPDATE_STATE_TRANSITION = 1;
private static final int UPDATE_STATE_UPDATED = 2;
/** /**
* Temporary display info, used for comparing display configurations. * Temporary display info, used for comparing display configurations.
*/ */
@@ -75,6 +90,11 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
*/ */
private final boolean mSingleDisplayDemoMode; private final boolean mSingleDisplayDemoMode;
/**
* True if the device can have more than one internal display on at a time.
*/
private final boolean mSupportsConcurrentInternalDisplays;
/** /**
* Map of all logical displays indexed by logical display id. * Map of all logical displays indexed by logical display id.
* Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache. * Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
@@ -89,13 +109,16 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private final DisplayDeviceRepository mDisplayDeviceRepo; private final DisplayDeviceRepository mDisplayDeviceRepo;
private final DeviceStateToLayoutMap mDeviceStateToLayoutMap; private final DeviceStateToLayoutMap mDeviceStateToLayoutMap;
private final Listener mListener; private final Listener mListener;
private final DisplayManagerService.SyncRoot mSyncRoot;
private final LogicalDisplayMapperHandler mHandler;
/** /**
* Has an entry for every logical display that the rest of the system has been notified about. * Has an entry for every logical display that the rest of the system has been notified about.
* Any entry in here requires us to send a {@link LOGICAL_DISPLAY_EVENT_REMOVED} event when it * Any entry in here requires us to send a {@link LOGICAL_DISPLAY_EVENT_REMOVED} event when it
* is deleted or {@link LOGICAL_DISPLAY_EVENT_CHANGED} when it is changed. * is deleted or {@link LOGICAL_DISPLAY_EVENT_CHANGED} when it is changed. The values are any
* of the {@code UPDATE_STATE_*} constant types.
*/ */
private final SparseBooleanArray mUpdatedLogicalDisplays = new SparseBooleanArray(); private final SparseIntArray mUpdatedLogicalDisplays = new SparseIntArray();
/** /**
* Keeps track of all the display groups that we already told other people about. IOW, if a * Keeps track of all the display groups that we already told other people about. IOW, if a
@@ -119,11 +142,18 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
private Layout mCurrentLayout = null; private Layout mCurrentLayout = null;
private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
LogicalDisplayMapper(DisplayDeviceRepository repo, Listener listener) { LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
@NonNull Handler handler) {
mSyncRoot = syncRoot;
mHandler = new LogicalDisplayMapperHandler(handler.getLooper());
mDisplayDeviceRepo = repo; mDisplayDeviceRepo = repo;
mListener = listener; mListener = listener;
mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false); mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
mSupportsConcurrentInternalDisplays = context.getResources().getBoolean(
com.android.internal.R.bool.config_supportsConcurrentInternalDisplays);
mDisplayDeviceRepo.addListener(this); mDisplayDeviceRepo.addListener(this);
mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(); mDeviceStateToLayoutMap = new DeviceStateToLayoutMap();
} }
@@ -142,6 +172,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked()); Slog.d(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked());
} }
finishStateTransitionLocked(false /*force*/);
updateLogicalDisplaysLocked(); updateLogicalDisplaysLocked();
break; break;
@@ -166,7 +197,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
public LogicalDisplay getDisplayLocked(DisplayDevice device) { public LogicalDisplay getDisplayLocked(DisplayDevice device) {
final int count = mLogicalDisplays.size(); final int count = mLogicalDisplays.size();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
LogicalDisplay display = mLogicalDisplays.valueAt(i); final LogicalDisplay display = mLogicalDisplays.valueAt(i);
if (display.getPrimaryDisplayDeviceLocked() == device) { if (display.getPrimaryDisplayDeviceLocked() == device) {
return display; return display;
} }
@@ -198,6 +229,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
} }
} }
@VisibleForTesting
public int getDisplayGroupIdFromDisplayIdLocked(int displayId) { public int getDisplayGroupIdFromDisplayIdLocked(int displayId) {
final LogicalDisplay display = getDisplayLocked(displayId); final LogicalDisplay display = getDisplayLocked(displayId);
if (display == null) { if (display == null) {
@@ -225,7 +257,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
ipw.increaseIndent(); ipw.increaseIndent();
ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode); ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
ipw.println("mCurrentLayout=" + mCurrentLayout); ipw.println("mCurrentLayout=" + mCurrentLayout);
final int logicalDisplayCount = mLogicalDisplays.size(); final int logicalDisplayCount = mLogicalDisplays.size();
@@ -244,19 +275,78 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
} }
void setDeviceStateLocked(int state) { void setDeviceStateLocked(int state) {
if (state != mDeviceState) { Slog.i(TAG, "Requesting Transition to state: " + state);
resetLayoutLocked(); // As part of a state transition, we may need to turn off some displays temporarily so that
mDeviceState = state; // the transition is smooth. Plus, on some devices, only one internal displays can be
applyLayoutLocked(); // on at a time. We use DISPLAY_PHASE_LAYOUT_TRANSITION to mark a display that needs to be
updateLogicalDisplaysLocked(); // temporarily turned off.
if (mDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) {
resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION);
}
mPendingDeviceState = state;
if (areAllTransitioningDisplaysOffLocked()) {
// Nothing to wait on, we're good to go
transitionToPendingStateLocked();
return;
}
if (DEBUG) {
Slog.d(TAG, "Postponing transition to state: " + mPendingDeviceState);
}
// Send the transitioning phase updates to DisplayManager so that the displays can
// start turning OFF in preparation for the new layout.
updateLogicalDisplaysLocked();
mHandler.sendEmptyMessageDelayed(MSG_TRANSITION_TO_PENDING_DEVICE_STATE,
TIMEOUT_STATE_TRANSITION_MILLIS);
}
private boolean areAllTransitioningDisplaysOffLocked() {
final int count = mLogicalDisplays.size();
for (int i = 0; i < count; i++) {
final LogicalDisplay display = mLogicalDisplays.valueAt(i);
if (display.getPhase() != LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) {
continue;
}
final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
if (device != null) {
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
if (info.state != Display.STATE_OFF) {
return false;
}
}
}
return true;
}
private void transitionToPendingStateLocked() {
resetLayoutLocked(mDeviceState, mPendingDeviceState, LogicalDisplay.DISPLAY_PHASE_ENABLED);
mDeviceState = mPendingDeviceState;
mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
applyLayoutLocked();
updateLogicalDisplaysLocked();
}
private void finishStateTransitionLocked(boolean force) {
if (mPendingDeviceState == DeviceStateManager.INVALID_DEVICE_STATE) {
return;
}
final boolean displaysOff = areAllTransitioningDisplaysOffLocked();
if (displaysOff || force) {
transitionToPendingStateLocked();
mHandler.removeMessages(MSG_TRANSITION_TO_PENDING_DEVICE_STATE);
} else if (DEBUG) {
Slog.d(TAG, "Not yet ready to transition to state=" + mPendingDeviceState
+ " with displays-off=" + displaysOff + " and force=" + force);
} }
} }
private void handleDisplayDeviceAddedLocked(DisplayDevice device) { private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked(); DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();
// Internal Displays need to have additional initialization. // Internal Displays need to have additional initialization.
// TODO: b/168208162 - This initializes a default dynamic display layout for INTERNAL // This initializes a default dynamic display layout for INTERNAL
// devices, which will eventually just be a fallback in case no static layout definitions // devices, which is used as a fallback in case no static layout definitions
// exist or cannot be loaded. // exist or cannot be loaded.
if (deviceInfo.type == Display.TYPE_INTERNAL) { if (deviceInfo.type == Display.TYPE_INTERNAL) {
initializeInternalDisplayDeviceLocked(device); initializeInternalDisplayDeviceLocked(device);
@@ -289,7 +379,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
display.updateLocked(mDisplayDeviceRepo); display.updateLocked(mDisplayDeviceRepo);
final DisplayInfo newDisplayInfo = display.getDisplayInfoLocked(); final DisplayInfo newDisplayInfo = display.getDisplayInfoLocked();
final boolean wasPreviouslyUpdated = mUpdatedLogicalDisplays.get(displayId); final int updateState = mUpdatedLogicalDisplays.get(displayId, UPDATE_STATE_NEW);
final boolean wasPreviouslyUpdated = updateState != UPDATE_STATE_NEW;
// The display is no longer valid and needs to be removed. // The display is no longer valid and needs to be removed.
if (!display.isValidLocked()) { if (!display.isValidLocked()) {
@@ -331,6 +422,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
assignDisplayGroupLocked(display); assignDisplayGroupLocked(display);
mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED); mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
} else if (updateState == UPDATE_STATE_TRANSITION) {
mLogicalDisplaysToUpdate.put(displayId,
LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION);
// Display frame rate overrides changed. // Display frame rate overrides changed.
} else if (!display.getPendingFrameRateOverrideUids().isEmpty()) { } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) {
mLogicalDisplaysToUpdate.put( mLogicalDisplaysToUpdate.put(
@@ -347,7 +442,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
} }
} }
mUpdatedLogicalDisplays.put(displayId, true); mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_UPDATED);
} }
// Go through the groups and do the same thing. We do this after displays since group // Go through the groups and do the same thing. We do this after displays since group
@@ -376,12 +471,13 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// Send the display and display group updates in order by message type. This is important // Send the display and display group updates in order by message type. This is important
// to ensure that addition and removal notifications happen in the right order. // to ensure that addition and removal notifications happen in the right order.
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION);
sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_ADDED); sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_ADDED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REMOVED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REMOVED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED); sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED);
sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_CHANGED); sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_CHANGED);
sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_REMOVED); sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_REMOVED);
@@ -400,7 +496,14 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
} }
final int id = mLogicalDisplaysToUpdate.keyAt(i); final int id = mLogicalDisplaysToUpdate.keyAt(i);
mListener.onLogicalDisplayEventLocked(getDisplayLocked(id), msg); final LogicalDisplay display = getDisplayLocked(id);
if (DEBUG) {
final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
final String uniqueId = device == null ? "null" : device.getUniqueId();
Slog.d(TAG, "Sending " + displayEventToString(msg) + " for display=" + id
+ " with device=" + uniqueId);
}
mListener.onLogicalDisplayEventLocked(display, msg);
if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) { if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) {
// We wait until we sent the EVENT_REMOVED event before actually removing the // We wait until we sent the EVENT_REMOVED event before actually removing the
// display. // display.
@@ -464,36 +567,81 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
} }
/** /**
* Resets the current layout in preparation for a new layout. Layouts can specify if some * Goes through all the displays used in the layouts for the specified {@code fromState} and
* displays should be disabled (OFF). When switching from one layout to another, we go * {@code toState} and applies the specified {@code phase}. When a new layout is requested, we
* through each of the displays and make sure any displays we might have disabled are * put the displays that will change into a transitional phase so that they can all be turned
* enabled again. * OFF. Once all are confirmed OFF, then this method gets called again to reset the phase to
* normal operation. This helps to ensure that all display-OFF requests are made before
* display-ON which in turn hides any resizing-jank windows might incur when switching displays.
*
* @param fromState The state we are switching from.
* @param toState The state we are switching to.
* @param phase The new phase to apply to the displays.
*/ */
private void resetLayoutLocked() { private void resetLayoutLocked(int fromState, int toState, @DisplayPhase int phase) {
final Layout layout = mDeviceStateToLayoutMap.get(mDeviceState); final Layout fromLayout = mDeviceStateToLayoutMap.get(fromState);
for (int i = layout.size() - 1; i >= 0; i--) { final Layout toLayout = mDeviceStateToLayoutMap.get(toState);
final Layout.Display displayLayout = layout.getAt(i);
final LogicalDisplay display = getDisplayLocked(displayLayout.getLogicalDisplayId()); final int count = mLogicalDisplays.size();
if (display != null) { for (int i = 0; i < count; i++) {
enableDisplayLocked(display, true); // Reset all displays back to enabled final LogicalDisplay logicalDisplay = mLogicalDisplays.valueAt(i);
final int displayId = logicalDisplay.getDisplayIdLocked();
final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
if (device == null) {
// If there's no device, then the logical display is due to be removed. Ignore it.
continue;
}
// Grab the display associations this display-device has in the old layout and the
// new layout.
final DisplayAddress address = device.getDisplayDeviceInfoLocked().address;
// Virtual displays do not have addresses.
final Layout.Display fromDisplay =
address != null ? fromLayout.getByAddress(address) : null;
final Layout.Display toDisplay =
address != null ? toLayout.getByAddress(address) : null;
// If a layout doesn't mention a display-device at all, then the display-device defaults
// to enabled. This is why we treat null as "enabled" in the code below.
final boolean wasEnabled = fromDisplay == null || fromDisplay.isEnabled();
final boolean willBeEnabled = toDisplay == null || toDisplay.isEnabled();
final boolean deviceHasNewLogicalDisplayId = fromDisplay != null && toDisplay != null
&& fromDisplay.getLogicalDisplayId() != toDisplay.getLogicalDisplayId();
// We consider a display-device as changing/transition if
// 1) It's already marked as transitioning
// 2) It's going from enabled to disabled
// 3) It's enabled, but it's mapped to a new logical display ID. To the user this
// would look like apps moving from one screen to another since task-stacks stay
// with the logical display [ID].
final boolean isTransitioning =
(logicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION)
|| (wasEnabled && !willBeEnabled)
|| (wasEnabled && deviceHasNewLogicalDisplayId);
if (isTransitioning) {
setDisplayPhase(logicalDisplay, phase);
if (phase == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) {
mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION);
}
} }
} }
} }
/** /**
* Apply (or reapply) the currently selected display layout. * Apply (or reapply) the currently selected display layout.
*/ */
private void applyLayoutLocked() { private void applyLayoutLocked() {
final Layout layout = mDeviceStateToLayoutMap.get(mDeviceState); final Layout oldLayout = mCurrentLayout;
mCurrentLayout = layout; mCurrentLayout = mDeviceStateToLayoutMap.get(mDeviceState);
Slog.i(TAG, "Applying the display layout for device state(" + mDeviceState Slog.i(TAG, "Applying layout: " + mCurrentLayout + ", Previous layout: " + oldLayout);
+ "): " + layout);
// Go through each of the displays in the current layout set. // Go through each of the displays in the current layout set.
final int size = layout.size(); final int size = mCurrentLayout.size();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
final Layout.Display displayLayout = layout.getAt(i); final Layout.Display displayLayout = mCurrentLayout.getAt(i);
// If the underlying display-device we want to use for this display // If the underlying display-device we want to use for this display
// doesn't exist, then skip it. This can happen at startup as display-devices // doesn't exist, then skip it. This can happen at startup as display-devices
@@ -521,8 +669,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
if (newDisplay != oldDisplay) { if (newDisplay != oldDisplay) {
newDisplay.swapDisplaysLocked(oldDisplay); newDisplay.swapDisplaysLocked(oldDisplay);
} }
enableDisplayLocked(newDisplay, displayLayout.isEnabled());
if (!displayLayout.isEnabled()) {
setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_DISABLED);
}
} }
} }
@@ -540,23 +692,23 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
display.updateLocked(mDisplayDeviceRepo); display.updateLocked(mDisplayDeviceRepo);
mLogicalDisplays.put(displayId, display); mLogicalDisplays.put(displayId, display);
enableDisplayLocked(display, device != null); setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_ENABLED);
return display; return display;
} }
private void enableDisplayLocked(LogicalDisplay display, boolean isEnabled) { private void setDisplayPhase(LogicalDisplay display, @DisplayPhase int phase) {
final int displayId = display.getDisplayIdLocked(); final int displayId = display.getDisplayIdLocked();
final DisplayInfo info = display.getDisplayInfoLocked(); final DisplayInfo info = display.getDisplayInfoLocked();
final boolean disallowSecondaryDisplay = mSingleDisplayDemoMode final boolean disallowSecondaryDisplay = mSingleDisplayDemoMode
&& (info.type != Display.TYPE_INTERNAL); && (info.type != Display.TYPE_INTERNAL);
if (isEnabled && disallowSecondaryDisplay) { if (phase != LogicalDisplay.DISPLAY_PHASE_DISABLED && disallowSecondaryDisplay) {
Slog.i(TAG, "Not creating a logical display for a secondary display because single" Slog.i(TAG, "Not creating a logical display for a secondary display because single"
+ " display demo mode is enabled: " + display.getDisplayInfoLocked()); + " display demo mode is enabled: " + display.getDisplayInfoLocked());
isEnabled = false; phase = LogicalDisplay.DISPLAY_PHASE_DISABLED;
} }
display.setEnabled(isEnabled); display.setPhase(phase);
} }
private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup) { private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup) {
@@ -564,14 +716,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
} }
private void initializeInternalDisplayDeviceLocked(DisplayDevice device) { private void initializeInternalDisplayDeviceLocked(DisplayDevice device) {
// We always want to make sure that our default display layout creates a logical // We always want to make sure that our default layout creates a logical
// display for every internal display device that is found. // display for every internal display device that is found.
// To that end, when we are notified of a new internal display, we add it to // To that end, when we are notified of a new internal display, we add it to
// the default definition if it is not already there. // the default layout definition if it is not already there.
final Layout layoutSet = mDeviceStateToLayoutMap.get(DeviceStateToLayoutMap.STATE_DEFAULT); final Layout layout = mDeviceStateToLayoutMap.get(DeviceStateToLayoutMap.STATE_DEFAULT);
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
final boolean isDefault = (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0; final boolean isDefault = (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
layoutSet.createDisplayLocked(info.address, isDefault, true /* isEnabled */); final boolean isEnabled = isDefault || mSupportsConcurrentInternalDisplays;
layout.createDisplayLocked(info.address, isDefault, isEnabled);
} }
private int assignLayerStackLocked(int displayId) { private int assignLayerStackLocked(int displayId) {
@@ -580,9 +733,45 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
return displayId; return displayId;
} }
private String displayEventToString(int msg) {
switch(msg) {
case LOGICAL_DISPLAY_EVENT_ADDED:
return "added";
case LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION:
return "transition";
case LOGICAL_DISPLAY_EVENT_CHANGED:
return "changed";
case LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED:
return "framerate_override";
case LOGICAL_DISPLAY_EVENT_SWAPPED:
return "swapped";
case LOGICAL_DISPLAY_EVENT_REMOVED:
return "removed";
}
return null;
}
public interface Listener { public interface Listener {
void onLogicalDisplayEventLocked(LogicalDisplay display, int event); void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
void onDisplayGroupEventLocked(int groupId, int event); void onDisplayGroupEventLocked(int groupId, int event);
void onTraversalRequested(); void onTraversalRequested();
} }
private class LogicalDisplayMapperHandler extends Handler {
LogicalDisplayMapperHandler(Looper looper) {
super(looper, null, true /*async*/);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_TRANSITION_TO_PENDING_DEVICE_STATE:
synchronized (mSyncRoot) {
finishStateTransitionLocked(true /*force*/);
}
break;
}
}
}
} }

View File

@@ -19,6 +19,7 @@ package com.android.server.display.layout;
import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.DEFAULT_DISPLAY;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Slog; import android.util.Slog;
import android.view.DisplayAddress; import android.view.DisplayAddress;
@@ -100,11 +101,28 @@ public class Layout {
* *
* @return The display corresponding to the specified display ID. * @return The display corresponding to the specified display ID.
*/ */
@Nullable
public Display getById(int id) { public Display getById(int id) {
for (int i = 0; i < mDisplays.size(); i++) { for (int i = 0; i < mDisplays.size(); i++) {
Display layout = mDisplays.get(i); Display display = mDisplays.get(i);
if (id == layout.getLogicalDisplayId()) { if (id == display.getLogicalDisplayId()) {
return layout; return display;
}
}
return null;
}
/**
* @param address The display address to check.
*
* @return The display corresponding to the specified address.
*/
@Nullable
public Display getByAddress(@NonNull DisplayAddress address) {
for (int i = 0; i < mDisplays.size(); i++) {
Display display = mDisplays.get(i);
if (address.equals(display.getAddress())) {
return display;
} }
} }
return null; return null;

View File

@@ -27,17 +27,20 @@ import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.PropertyInvalidatedCache; import android.app.PropertyInvalidatedCache;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Parcel; import android.os.Parcel;
import android.os.Process; import android.os.Process;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import android.view.Display; import android.view.Display;
import android.view.DisplayAddress; import android.view.DisplayAddress;
import android.view.DisplayInfo; import android.view.DisplayInfo;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4; import androidx.test.runner.AndroidJUnit4;
@@ -61,9 +64,12 @@ public class LogicalDisplayMapperTest {
private DisplayDeviceRepository mDisplayDeviceRepo; private DisplayDeviceRepository mDisplayDeviceRepo;
private LogicalDisplayMapper mLogicalDisplayMapper; private LogicalDisplayMapper mLogicalDisplayMapper;
private Context mContext; private TestLooper mLooper;
private Handler mHandler;
@Mock LogicalDisplayMapper.Listener mListenerMock; @Mock LogicalDisplayMapper.Listener mListenerMock;
@Mock Context mContextMock;
@Mock Resources mResourcesMock;
@Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor; @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
@@ -73,7 +79,6 @@ public class LogicalDisplayMapperTest {
System.setProperty("dexmaker.share_classloader", "true"); System.setProperty("dexmaker.share_classloader", "true");
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getContext();
mDisplayDeviceRepo = new DisplayDeviceRepository( mDisplayDeviceRepo = new DisplayDeviceRepository(
new DisplayManagerService.SyncRoot(), new DisplayManagerService.SyncRoot(),
new PersistentDataStore(new PersistentDataStore.Injector() { new PersistentDataStore(new PersistentDataStore.Injector() {
@@ -94,7 +99,15 @@ public class LogicalDisplayMapperTest {
// Disable binder caches in this process. // Disable binder caches in this process.
PropertyInvalidatedCache.disableForTestMode(); PropertyInvalidatedCache.disableForTestMode();
mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo, mListenerMock); when(mContextMock.getResources()).thenReturn(mResourcesMock);
when(mResourcesMock.getBoolean(
com.android.internal.R.bool.config_supportsConcurrentInternalDisplays))
.thenReturn(true);
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
mListenerMock, new DisplayManagerService.SyncRoot(), mHandler);
} }
@@ -299,7 +312,7 @@ public class LogicalDisplayMapperTest {
private DisplayDeviceInfo mSentInfo; private DisplayDeviceInfo mSentInfo;
TestDisplayDevice() { TestDisplayDevice() {
super(null, null, "test_display_" + sUniqueTestDisplayId++, mContext); super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock);
mInfo = new DisplayDeviceInfo(); mInfo = new DisplayDeviceInfo();
} }