blob: 5bd4088d72be7618667a543b3e6083d72114163c [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.display;
import android.content.Context;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayEventReceiver;
import android.view.DisplayInfo;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Consumer;
/**
* Responsible for creating {@link LogicalDisplay}s and associating them to the
* {@link DisplayDevice} objects supplied through {@link DisplayAdapter.Listener}.
*
* Additionally this class will keep track of which {@link DisplayGroup} each
* {@link LogicalDisplay} belongs to.
*
* For devices with a single internal display, the mapping is done once and left
* alone. For devices with multiple built-in displays, such as foldable devices,
* {@link LogicalDisplay}s can be remapped to different {@link DisplayDevice}s.
*/
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;
public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5;
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_REMOVED = 3;
/**
* Temporary display info, used for comparing display configurations.
*/
private final DisplayInfo mTempDisplayInfo = new DisplayInfo();
private final DisplayInfo mTempNonOverrideDisplayInfo = new DisplayInfo();
/**
* True if the display mapper service should pretend there is only one display
* and only tell applications about the existence of the default logical display.
* The display manager can still mirror content to secondary displays but applications
* cannot present unique content on those displays.
* Used for demonstration purposes only.
*/
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.
* TODO: multi-display - Move the aforementioned comment?
*/
private final SparseArray<LogicalDisplay> mLogicalDisplays =
new SparseArray<LogicalDisplay>();
private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
private int mNextNonDefaultGroupId = DisplayGroup.DEFAULT + 1;
/** A mapping from logical display id to display group. */
private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>();
private final DisplayDeviceRepository mDisplayDeviceRepo;
private final Listener mListener;
LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) {
mDisplayDeviceRepo = repo;
mListener = listener;
mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
mDisplayDeviceRepo.addListener(this);
loadFoldedDisplayConfig(context);
}
@Override
public void onDisplayDeviceEventLocked(DisplayDevice device, int event) {
switch (event) {
case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_ADDED:
handleDisplayDeviceAddedLocked(device);
break;
case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_CHANGED:
updateLogicalDisplaysLocked();
break;
case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_REMOVED:
updateLogicalDisplaysLocked();
break;
}
}
@Override
public void onTraversalRequested() {
mListener.onTraversalRequested();
}
public LogicalDisplay getLocked(int displayId) {
return mLogicalDisplays.get(displayId);
}
public LogicalDisplay getLocked(DisplayDevice device) {
final int count = mLogicalDisplays.size();
for (int i = 0; i < count; i++) {
LogicalDisplay display = mLogicalDisplays.valueAt(i);
if (display.getPrimaryDisplayDeviceLocked() == device) {
return display;
}
}
return null;
}
public int[] getDisplayIdsLocked(int callingUid) {
final int count = mLogicalDisplays.size();
int[] displayIds = new int[count];
int n = 0;
for (int i = 0; i < count; i++) {
LogicalDisplay display = mLogicalDisplays.valueAt(i);
DisplayInfo info = display.getDisplayInfoLocked();
if (info.hasAccess(callingUid)) {
displayIds[n++] = mLogicalDisplays.keyAt(i);
}
}
if (n != count) {
displayIds = Arrays.copyOfRange(displayIds, 0, n);
}
return displayIds;
}
public void forEachLocked(Consumer<LogicalDisplay> consumer) {
final int count = mLogicalDisplays.size();
for (int i = 0; i < count; i++) {
consumer.accept(mLogicalDisplays.valueAt(i));
}
}
public DisplayGroup getDisplayGroupLocked(int groupId) {
final int size = mDisplayIdToGroupMap.size();
for (int i = 0; i < size; i++) {
final DisplayGroup displayGroup = mDisplayIdToGroupMap.valueAt(i);
if (displayGroup.getGroupId() == groupId) {
return displayGroup;
}
}
return null;
}
public void dumpLocked(PrintWriter pw) {
pw.println("LogicalDisplayMapper:");
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.increaseIndent();
ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
ipw.println("mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
final int logicalDisplayCount = mLogicalDisplays.size();
ipw.println();
ipw.println("Logical Displays: size=" + logicalDisplayCount);
for (int i = 0; i < logicalDisplayCount; i++) {
int displayId = mLogicalDisplays.keyAt(i);
LogicalDisplay display = mLogicalDisplays.valueAt(i);
ipw.println("Display " + displayId + ":");
ipw.increaseIndent();
display.dumpLocked(ipw);
ipw.decreaseIndent();
ipw.println();
}
}
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 || displayUnfolded == 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());
}
}
void setFoldOverrideLocked(Boolean isFolded) {
if (!Objects.equals(isFolded, mIsFoldedOverride)) {
mIsFoldedOverride = isFolded;
setDeviceFoldedLocked(mIsFolded);
}
}
private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();
boolean isDefault = (deviceInfo.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) {
Slog.w(TAG, "Ignoring attempt to add a second default display: " + deviceInfo);
isDefault = false;
}
if (!isDefault && mSingleDisplayDemoMode) {
Slog.i(TAG, "Not creating a logical display for a secondary display "
+ " because single display demo mode is enabled: " + deviceInfo);
return;
}
final int displayId = assignDisplayIdLocked(isDefault);
final int layerStack = assignLayerStackLocked(displayId);
LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
display.updateLocked(mDisplayDeviceRepo);
if (!display.isValidLocked()) {
// This should never happen currently.
Slog.w(TAG, "Ignoring display device because the logical display "
+ "created from it was not considered valid: " + deviceInfo);
return;
}
mLogicalDisplays.put(displayId, display);
final DisplayGroup displayGroup;
final boolean addNewDisplayGroup =
isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0;
if (addNewDisplayGroup) {
final int groupId = assignDisplayGroupIdLocked(isDefault);
displayGroup = new DisplayGroup(groupId);
} else {
displayGroup = mDisplayIdToGroupMap.get(Display.DEFAULT_DISPLAY);
}
displayGroup.addDisplayLocked(display);
mDisplayIdToGroupMap.append(displayId, displayGroup);
if (addNewDisplayGroup) {
// Group added events happen before Logical Display added events.
mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
}
mListener.onLogicalDisplayEventLocked(display,
LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED);
if (!addNewDisplayGroup) {
// Group changed events happen after Logical Display added events.
mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
}
}
/**
* Updates all existing logical displays given the current set of display devices.
* Removes invalid logical displays. Sends notifications if needed.
*/
private void updateLogicalDisplaysLocked() {
for (int i = mLogicalDisplays.size() - 1; i >= 0; i--) {
final int displayId = mLogicalDisplays.keyAt(i);
LogicalDisplay display = mLogicalDisplays.valueAt(i);
mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked());
display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo);
DisplayEventReceiver.FrameRateOverride[] frameRatesOverrides =
display.getFrameRateOverrides();
display.updateLocked(mDisplayDeviceRepo);
final DisplayGroup changedDisplayGroup;
if (!display.isValidLocked()) {
mLogicalDisplays.removeAt(i);
final DisplayGroup displayGroup = mDisplayIdToGroupMap.removeReturnOld(displayId);
displayGroup.removeDisplayLocked(display);
mListener.onLogicalDisplayEventLocked(display,
LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED);
changedDisplayGroup = displayGroup;
} else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
final int flags = display.getDisplayInfoLocked().flags;
final DisplayGroup defaultDisplayGroup = mDisplayIdToGroupMap.get(
Display.DEFAULT_DISPLAY);
if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) {
// The display should have its own DisplayGroup.
if (defaultDisplayGroup.removeDisplayLocked(display)) {
final int groupId = assignDisplayGroupIdLocked(false);
final DisplayGroup displayGroup = new DisplayGroup(groupId);
displayGroup.addDisplayLocked(display);
mDisplayIdToGroupMap.append(display.getDisplayIdLocked(), displayGroup);
mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
changedDisplayGroup = defaultDisplayGroup;
} else {
changedDisplayGroup = null;
}
} else {
// The display should be a part of the default DisplayGroup.
final DisplayGroup displayGroup = mDisplayIdToGroupMap.get(displayId);
if (displayGroup != defaultDisplayGroup) {
displayGroup.removeDisplayLocked(display);
defaultDisplayGroup.addDisplayLocked(display);
mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
mDisplayIdToGroupMap.put(displayId, defaultDisplayGroup);
changedDisplayGroup = displayGroup;
} else {
changedDisplayGroup = null;
}
}
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 if (!display.getPendingFrameRateOverrideUids().isEmpty()) {
mListener.onLogicalDisplayEventLocked(display,
LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
changedDisplayGroup = null;
} 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
// things like display cutouts.
display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo);
if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) {
mListener.onLogicalDisplayEventLocked(display,
LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED);
}
changedDisplayGroup = null;
}
// CHANGED and REMOVED DisplayGroup events should always fire after Display events.
if (changedDisplayGroup != null) {
final int event = changedDisplayGroup.isEmptyLocked()
? LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED
: LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED;
mListener.onDisplayGroupEventLocked(changedDisplayGroup.getGroupId(), event);
}
}
}
private int assignDisplayIdLocked(boolean isDefault) {
return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
}
private int assignDisplayGroupIdLocked(boolean isDefault) {
return isDefault ? DisplayGroup.DEFAULT : mNextNonDefaultGroupId++;
}
private int assignLayerStackLocked(int displayId) {
// Currently layer stacks and display ids are the same.
// This need not be the case.
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 onDisplayGroupEventLocked(int groupId, int event);
void onTraversalRequested();
}
}