Android multi-screen support-Android12
android12-release
1. Overview and related articles
AOSP > Documentation > Heart Themes > Multiple Screen Overview
Terminology
In these articles, primary and secondary screens are defined as follows:for primary (default) screen is
屏幕 ID
forDEFAULT_DISPLAY
secondary screen屏幕 ID
is notDEFAULT_DISPLAY
subject area | article |
---|---|
development and testing | Recommended Practices Testing and Development Environments FAQ |
Collection of related articles | Display system decoration support input method support |
single article | Multiple recovery activities, startup policies, lock screen, input routing , multi-zone audio |
2. Screen window configuration
2.1 Configuration xml file
/data/system/display_settings.xml
Configuration:
- Simulation Screen :
uniqueId
Used to identify the screen in the name attribute, for a simulation screen this ID isoverlay:1
.
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<display-settings>
<config identifier="0" />
<display
name="overlay:1"
shouldShowSystemDecors="true"
shouldShowIme="true" />
</display-settings>
- Built-in screens :
uniqueId
For built-in screens, a sample value could be "local:45354385242535243453". Another way is to use the hardware port information and set identifier="1" to correspond to DisplayWindowSettingsProvider#IDENTIFIER_PORT, then update name to use"port:<port_id>" 格式
.
<?xmlversion='1.0' encoding='utf-8' standalone='yes' ?>
<display-settings>
<config identifier="1" />
<display
name="port:12345"
shouldShowSystemDecors="true"
shouldShowIme="true" />
</display-settings>
2.2 DisplayInfo#uniqueId screen identification
DisplayInfo#uniqueId to add a stable identifier and differentiate between local, network, and virtual screens
Screen type | Format |
---|---|
local | local:<stable-id> |
network | network:<mac-address> |
virtual | virtual:<package-name-and-name> |
2.3 adb view information
$ dumpsys SurfaceFlinger --display-id
# Example output.
Display 21691504607621632 (HWC display 0): port=0 pnpId=SHP displayName="LQ123P1JX32"
Display 9834494747159041 (HWC display 2): port=1 pnpId=HWP displayName="HP Z24i"
Display 1886279400700944 (HWC display 1): port=2 pnpId=AUS displayName="ASUS MB16AP"
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp#SurfaceFlinger::dumpDisplayIdentificationData
void SurfaceFlinger::dumpDisplayIdentificationData(std::string& result) const {
for (const auto& [token, display] : mDisplays) {
const auto displayId = PhysicalDisplayId::tryCast(display->getId());
if (!displayId) {
continue;
}
const auto hwcDisplayId = getHwComposer().fromPhysicalDisplayId(*displayId);
if (!hwcDisplayId) {
continue;
}
StringAppendF(&result,
"Display %s (HWC display %" PRIu64 "): ", to_string(*displayId).c_str(),
*hwcDisplayId);
uint8_t port;
DisplayIdentificationData data;
if (!getHwComposer().getDisplayIdentificationData(*hwcDisplayId, &port, &data)) {
result.append("no identification data\n");
continue;
}
if (!isEdid(data)) {
result.append("unknown identification data\n");
continue;
}
const auto edid = parseEdid(data);
if (!edid) {
result.append("invalid EDID\n");
continue;
}
StringAppendF(&result, "port=%u pnpId=%s displayName=\"", port, edid->pnpId.data());
result.append(edid->displayName.data(), edid->displayName.length());
result.append("\"\n");
}
}
3. Configuration file analysis
3.1 xml field reading
- File path:
DATA_DISPLAY_SETTINGS_FILE_PATH = "system/display_settings.xml"
,VENDOR_DISPLAY_SETTINGS_FILE_PATH = "etc/display_settings.xml"
,Settings.Global.getString(resolver,DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH)
(DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH = "wm_display_settings_path"
)FileData
Objects:fileData.mIdentifierType = getIntAttribute(parser, "identifier", IDENTIFIER_UNIQUE_ID)
,name = parser.getAttributeValue(null, "name")
,shouldShowIme = getBooleanAttribute(parser, "shouldShowIme", null /* defaultValue */)
,settingsEntry.mShouldShowSystemDecors = getBooleanAttribute(parser, "shouldShowSystemDecors", null /* defaultValue */)
etc.private static final class FileData { int mIdentifierType; final Map<String, SettingsEntry> mSettings = new HashMap<>(); @Override public String toString() { return "FileData{" + "mIdentifierType=" + mIdentifierType + ", mSettings=" + mSettings + '}'; } }
DisplayWindowSettings.java
The current persistent settings for the monitor. Provides a strategy for displaying settings and delegates persistence and lookup of setting values to the provided{@link SettingsProvider}
@Nullable
private static FileData readSettings(ReadableSettingsStorage storage) {
InputStream stream;
try {
stream = storage.openRead();
} catch (IOException e) {
Slog.i(TAG, "No existing display settings, starting empty");
return null;
}
FileData fileData = new FileData();
boolean success = false;
try {
TypedXmlPullParser parser = Xml.resolvePullParser(stream);
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Do nothing.
}
if (type != XmlPullParser.START_TAG) {
throw new IllegalStateException("no start tag found");
}
int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (tagName.equals("display")) {
readDisplay(parser, fileData);
} else if (tagName.equals("config")) {
readConfig(parser, fileData);
} else {
Slog.w(TAG, "Unknown element under <display-settings>: "
+ parser.getName());
XmlUtils.skipCurrentTag(parser);
}
}
success = true;
} catch (IllegalStateException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (NullPointerException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (NumberFormatException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (XmlPullParserException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (IOException e) {
Slog.w(TAG, "Failed parsing " + e);
} catch (IndexOutOfBoundsException e) {
Slog.w(TAG, "Failed parsing " + e);
} finally {
try {
stream.close();
} catch (IOException ignored) {
}
}
if (!success) {
fileData.mSettings.clear();
}
return fileData;
}
private static int getIntAttribute(TypedXmlPullParser parser, String name, int defaultValue) {
return parser.getAttributeInt(null, name, defaultValue);
}
@Nullable
private static Integer getIntegerAttribute(TypedXmlPullParser parser, String name,
@Nullable Integer defaultValue) {
try {
return parser.getAttributeInt(null, name);
} catch (Exception ignored) {
return defaultValue;
}
}
@Nullable
private static Boolean getBooleanAttribute(TypedXmlPullParser parser, String name,
@Nullable Boolean defaultValue) {
try {
return parser.getAttributeBoolean(null, name);
} catch (Exception ignored) {
return defaultValue;
}
}
private static void readDisplay(TypedXmlPullParser parser, FileData fileData)
throws NumberFormatException, XmlPullParserException, IOException {
String name = parser.getAttributeValue(null, "name");
if (name != null) {
SettingsEntry settingsEntry = new SettingsEntry();
settingsEntry.mWindowingMode = getIntAttribute(parser, "windowingMode",
WindowConfiguration.WINDOWING_MODE_UNDEFINED /* defaultValue */);
settingsEntry.mUserRotationMode = getIntegerAttribute(parser, "userRotationMode",
null /* defaultValue */);
settingsEntry.mUserRotation = getIntegerAttribute(parser, "userRotation",
null /* defaultValue */);
settingsEntry.mForcedWidth = getIntAttribute(parser, "forcedWidth",
0 /* defaultValue */);
settingsEntry.mForcedHeight = getIntAttribute(parser, "forcedHeight",
0 /* defaultValue */);
settingsEntry.mForcedDensity = getIntAttribute(parser, "forcedDensity",
0 /* defaultValue */);
settingsEntry.mForcedScalingMode = getIntegerAttribute(parser, "forcedScalingMode",
null /* defaultValue */);
settingsEntry.mRemoveContentMode = getIntAttribute(parser, "removeContentMode",
REMOVE_CONTENT_MODE_UNDEFINED /* defaultValue */);
settingsEntry.mShouldShowWithInsecureKeyguard = getBooleanAttribute(parser,
"shouldShowWithInsecureKeyguard", null /* defaultValue */);
settingsEntry.mShouldShowSystemDecors = getBooleanAttribute(parser,
"shouldShowSystemDecors", null /* defaultValue */);
final Boolean shouldShowIme = getBooleanAttribute(parser, "shouldShowIme",
null /* defaultValue */);
if (shouldShowIme != null) {
settingsEntry.mImePolicy = shouldShowIme ? DISPLAY_IME_POLICY_LOCAL
: DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
} else {
settingsEntry.mImePolicy = getIntegerAttribute(parser, "imePolicy",
null /* defaultValue */);
}
settingsEntry.mFixedToUserRotation = getIntegerAttribute(parser, "fixedToUserRotation",
null /* defaultValue */);
settingsEntry.mIgnoreOrientationRequest = getBooleanAttribute(parser,
"ignoreOrientationRequest", null /* defaultValue */);
settingsEntry.mIgnoreDisplayCutout = getBooleanAttribute(parser,
"ignoreDisplayCutout", null /* defaultValue */);
settingsEntry.mDontMoveToTop = getBooleanAttribute(parser,
"dontMoveToTop", null /* defaultValue */);
fileData.mSettings.put(name, settingsEntry);
}
XmlUtils.skipCurrentTag(parser);
}
private static void readConfig(TypedXmlPullParser parser, FileData fileData)
throws NumberFormatException,
XmlPullParserException, IOException {
fileData.mIdentifierType = getIntAttribute(parser, "identifier",
IDENTIFIER_UNIQUE_ID);
XmlUtils.skipCurrentTag(parser);
}
3.2 Brief timing diagram
4. Per-screen focus
To support multiple input sources targeting a single screen simultaneously, Android 10 can be configured to support multiple focus windows, up to one per screen. This feature only works on special types of devices when multiple users are interacting with the same device at the same time and using different input methods or devices (such as Android Automotive).
强烈建议不要为常规设备启用此功能,包括跨屏设备或用于类似桌面设备体验的设备。这主要是出于安全方面的考虑,因为这样做可能会导致用户不确定哪个窗口具有输入焦点
。
Imagine a user entering secure information into a text input field, perhaps logging into a banking app or entering text that contains sensitive information. A malicious application can create a virtual off-screen screen for executing activities, or it can use text input fields to execute activities. Both the legitimate and malicious activities have focus and both display a valid input indicator (blinking cursor).
However, keyboard (hardware or software) input can only enter the top-level activity (the most recently launched application). By creating a hidden virtual screen, a malicious app can obtain user input even when using the software keyboard on the main device screen.
Use to
com.android.internal.R.bool.config_perDisplayFocusEnabled
set per-screen focus.
兼容性
**Issue:** In Android 9 and lower, at most one window in the system has focus at a time.**Solution:** In the rare case where two windows from the same process are both focused, the system provides focus only to the window higher in the Z-order. This restriction has been removed for apps targeting Android 10, and it is expected that these apps can support focusing multiple windows at the same time.
实现
WindowManagerService#mPerDisplayFocusEnabled
Used to control the availability of this feature. InActivityManager
, the system now usesActivityDisplay#getFocusedStack()
global tracking instead of utilizing variables.ActivityDisplay#getFocusedStack()
Determine focus based on Z-axis order, not by cached values. This way, only one sourceWindowManager
needs to track the activity's Z-order.If it is necessary to determine the topmost focus stack in the system,
ActivityStackSupervisor#getTopDisplayFocusedStack()
a similar approach is used to handle these cases. The system will traverse these stacks from top to bottom, searching for the first stack that meets the criteria.
InputDispatcher
It is now possible to have multiple focus windows (one per screen). If an input event is specific to a screen, the event is dispatched to the focused window in that screen. Otherwise, it is dispatched to the focused window in the focused screen (that is, the screen the user most recently interacted with).See
InputDispatcher::mFocusedWindowHandlesByDisplay 和 InputDispatcher::setFocusedDisplay()
. Spotlight apps are also updated individually viaNativeInputManager::setFocusedApplication()
in .InputManagerService
In
WindowManager
, the focused window is also tracked separately. SeeDisplayContent#mCurrentFocus
andDisplayContent#mFocusedApp
and their respective uses. The related focus tracking and update methods have beenWindowManagerService
moved from toDisplayContent
.