Android多屏幕支持-Android12

news2024/11/25 11:57:59

Android多屏幕支持-Android12

  • 1、概览及相关文章
  • 2、屏幕窗口配置
    • 2.1 配置xml文件
    • 2.2 DisplayInfo#uniqueId 屏幕标识
    • 2.3 adb查看信息
  • 3、配置文件解析
    • 3.1 xml字段读取
    • 3.2 简要时序图
  • 4、每屏幕焦点

android12-release


1、概览及相关文章

AOSP > 文档 > 心主题 > 多屏幕概览

术语
在这些文章中,主屏幕和辅助屏幕的定义如下:

主(默认)屏幕的屏幕 IDDEFAULT_DISPLAY
辅助屏幕的屏幕 ID 不是 DEFAULT_DISPLAY

主题区域文章
开发和测试推荐做法
测试和开发环境
常见问题解答
相关文章集显示
系统装饰支持
输入法支持
单篇文章多项恢复
Activity 启动政策
锁定屏幕
输入路由
多区音频

2、屏幕窗口配置

2.1 配置xml文件

/data/system/display_settings.xml 配置:

  • 模拟屏幕uniqueId 用于在名称属性中标识屏幕,对于模拟屏幕,此 ID 为 overlay: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>
  • 内置屏幕uniqueId 对于内置屏幕,示例值可以是 “local:45354385242535243453”。另一种方式是使用硬件端口信息,并设置 identifier=“1” 以与 DisplayWindowSettingsProvider#IDENTIFIER_PORT 对应,然后更新 name 以使用 "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 屏幕标识

DisplayInfo#uniqueId,以添加稳定的标识符并区分本地、网络和虚拟屏幕

屏幕类型格式
本地local:<stable-id>
网络network:<mac-address>
虚拟virtual:<package-name-and-name>

2.3 adb查看信息

$ 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、配置文件解析

3.1 xml字段读取

  • 文件路径: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对象: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 */)等等
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有关显示器的当前持久设置。提供显示设置的策略,并将设置值的持久性和查找委派给提供的{@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 简要时序图

在这里插入图片描述

4、每屏幕焦点

每个屏幕焦点

为了同时支持多个以单个屏幕为目标的输入源,可以将 Android 10 配置为支持多个聚焦窗口,每个屏幕最多支持一个。当多个用户同时与同一设备交互并使用不同的输入方法或设备(例如 Android Automotive)时,此功能仅适用于特殊类型的设备。

强烈建议不要为常规设备启用此功能,包括跨屏设备或用于类似桌面设备体验的设备。这主要是出于安全方面的考虑,因为这样做可能会导致用户不确定哪个窗口具有输入焦点

想象一下,用户在文本输入字段中输入安全信息,也许是登录某个银行应用或者输入包含敏感信息的文本。恶意应用可以创建一个虚拟的屏幕外屏幕用于执行 activity,也可以使用文本输入字段执行 activity。合法 activity 和恶意 activity 均具有焦点,并且都显示一个有效的输入指示符(闪烁光标)。

不过,键盘(硬件或软件)的输入只能进入最顶层的 activity(最近启动的应用)。通过创建隐藏的虚拟屏幕,即使在主设备屏幕上使用软件键盘,恶意应用也可以获取用户输入。

使用 com.android.internal.R.bool.config_perDisplayFocusEnabled 设置每屏幕焦点。

兼容性
**问题:**在 Android 9 及更低版本中,系统中一次最多只有一个窗口具有焦点。

**解决方案:**在极少数情况下,来自同一进程的两个窗口都处于聚焦状态,则系统仅向在 Z 轴顺序中较高的窗口提供焦点。对于以 Android 10 为目标平台的应用,目前已取消这一限制,此时预计这些应用可以支持同时聚焦多个窗口。

实现
WindowManagerService#mPerDisplayFocusEnabled 用于控制此功能的可用性。在 ActivityManager 中,系统现在使用的是 ActivityDisplay#getFocusedStack(),而不是利用变量进行全局跟踪。ActivityDisplay#getFocusedStack() 根据 Z 轴顺序确定焦点,而不是通过缓存值来确定。这样一来,只有一个来源 WindowManager 需要跟踪 activity 的 Z 轴顺序。

如果必须要确定系统中最顶层的聚焦堆栈,ActivityStackSupervisor#getTopDisplayFocusedStack() 会采用类似的方法处理这些情况。系统将从上到下遍历这些堆栈,搜索第一个符合条件的堆栈。

InputDispatcher 现在可以有多个聚焦窗口(每个屏幕一个)。如果某个输入事件特定于屏幕,则该事件会被分派到相应屏幕中的聚焦窗口。否则,它会被分派到聚焦屏幕(即用户最近与之交互的屏幕)中的聚焦窗口。

请参阅 InputDispatcher::mFocusedWindowHandlesByDisplay 和 InputDispatcher::setFocusedDisplay()。聚焦应用也会通过 NativeInputManager::setFocusedApplication()InputManagerService 中分别更新。

WindowManager 中,系统还会单独跟踪聚焦窗口。请参阅 DisplayContent#mCurrentFocusDisplayContent#mFocusedApp 以及各自的用途。相关的焦点跟踪和更新方法已从 WindowManagerService 移至 DisplayContent

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/873132.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

206、仿真-51单片机锂电池蓄电池电压电流加按键控制开关状态Proteus仿真设计(程序+Proteus仿真+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 四、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&a…

基于STM32CUBEMX驱动TMOS模块STHS34PF80(1)----获取ID

基于STM32CUBEMX驱动TMOS模块STHS34PF80----1.获取ID 概述样品申请视频教程所有功能接口最小系统图生成STM32CUBEMX串口配置IIC配置IO口设置串口重定向 模块地址参考demoIIC写函数IIC读函数参考程序初始化获取ID主函数 概述 STHS34PF80 是一款非冷却、工厂校准的红外运动和存在…

变压器保护高侧过流保护整定原则

电流速断保护的动作电流可按下列两个条件来选择&#xff1a; &#xff08;1&#xff09; 躲过厂用变压器负荷侧母线上短路时流过保护装置的最大短路电流。 动作电流整定为&#xff1a; IsdKkIDmax &#xff08;1-1&#xff09; 式中 Kk——可靠系数&#xff0c; 一般取1.3&…

Oracle将与Kubernetes合作推出DevOps解决方案!

导读Oracle想成为云计算领域的巨头&#xff0c;但它不是推出自己品牌的云DevOps软件&#xff0c;而是将与CoreOS在Kubernetes端展开合作。七年前&#xff0c;Oracle想要成为Linux领域的一家重量级公司。于是&#xff0c;Oracle主席拉里埃利森&#xff08;Larry Ellison&#xf…

【Python】如何判断时间序列数据是否为平稳时间序列或非平稳时间序列?

判断时间序列数据是否为平稳时间序列或非平稳时间序列&#xff0c;通常可以通过以下方法&#xff1a; &#xff08;1&#xff09;观察时间序列数据的均值和方差是否随时间变化而发生明显的改变。若均值和方差变化明显&#xff0c;则该时间序列数据可能为非平稳时间序列&#x…

章节5:Burp 扫描功能

章节5&#xff1a;Burp 扫描功能 参考资料 https://portswigger.net/burp/documentation/scanner https://portswigger.net/burp/documentation/desktop/scanning 模块总体介绍&#xff1a; https://portswigger.net/burp/vulnerability-scanner 扫描功能的使用&#xff…

深入了解 Vue 3 组件间通信机制

什么是组件&#xff1f; 在 Vue3 中&#xff0c;组件是构建应用界面的核心概念之一。组件可以看作是可复用、自包含和可组合的代码块&#xff0c;用于封装 UI 元素和相应的行为逻辑。 通俗来说就是&#xff0c;组件&#xff08;Component&#xff09;是一种对数据和方法的简单…

置信域策略优化Trust Region Policy Optimization (TRPO)

1. 置信域方法(Trust Region Methods) [1]将置信域方法用到强化学习中&#xff0c;并取到了非常好的结果. 1.1 优化问题 1.2 置信域 1.3 置信域方法的过程 References [1] Schulman J, Levine S, Abbeel P, et al. Trust region policy optimization[C]//International conf…

旋转图像(旋转矩阵)

原题链接 旋转图像备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能&#xff0c;轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/rotate-image/ 算法分析 若矩阵的行列数为N&#xff0c;设i表示行索引&#xff0c;i属…

如何定位线上CPU飙高的问题

1.问题情景 我们的接口卡死&#xff0c;CPU飙高到打不开的网页 2.问题定位 2.1 top指令 通过top命令找到CPU耗用最厉害的那个进程的PID 直接输入top Linux下的100%代表一个核心&#xff0c;如果是八核&#xff0c;最高可以到800%&#xff0c;这样才算满 然后通过PID找到CP…

设计模式再探——策略模式

目录 一、背景介绍二、思路&方案三、过程1.策略模式简介2.策略模式的类图3.策略模式代码4.策略模式还可以优化的地方5.策略模式的例子改造(配置文件反射) 四、总结五、升华 一、背景介绍 最近在做产品的过程中&#xff0c;对于主题讨论回复内容&#xff0c;按照追评次数排…

Java学习手册——第一篇Java简介

今后Java学习手册就来给大家梳理JavaSE的基础知识啦&#xff0c; 除了这个专栏我们还有其他专栏&#xff1a;前端、安全、后端等。 希望大家可以在这里一起讨论学习哟~ Java学习手册——第一篇Java简介 1. Java基础知识2. Java能干嘛3. Java基础环境搭建 1. Java基础知识 出生…

BANI时代下的项目管理:如何迎接挑战与机遇

BANI时代的介绍与特点 BANI时代的起源 BANI这个概念首次被提出是为了描述和理解我们所处的复杂、多变、不确定的时代。与VUCA&#xff08;Volatile, Uncertain, Complex, Ambiguous&#xff09;相比&#xff0c;BANI更加准确地捕捉了我们面临的现实挑战。VUCA重点关注外部环境…

【基础操作】Linux打开terminal,Anaconda默认进入的虚拟环境(python版本)设置(自行指定)

为了免除每次打开terminal都要输入 conda activate … 的麻烦&#xff0c;可以这么设置。 1. 打开terminal&#xff0c;然后输入命令 vim ~/.bashrc2. 然后在文件末尾添加 conda activate your_envs # your_envs是你的虚拟环境名称3. 保存退出&#xff0c;重新打开就成功啦…

第八课 双重所有格和不定代词

系列文章目录 文章目录 系列文章目录前言一、of s 的所有格1、of 有生命的名词 ’s2、of 名词性物主代词3、小结 二、反身代词1、作宾语和介词宾语2、作表语3、作固定惯语 三、相互代词四、指示代词 Such 和 Same 的用法 前言 一、of s 的所有格 1、of 有生命的名词 ’s 2、…

大数据课程I3——Kafka的消息流与索引机制

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 掌握Kafka的消息流处理; ⚪ 掌握Kafka的索引机制; ⚪ 掌握Kafka的消息系统语义; 一、Kafka消息流处理 1. Producer 写入消息 流程说明: 1. producer 要向Kafka生产消息,需要先通过…

ORB-SLAM2第二节---双目地图初始化

比起单目初始化&#xff0c;而双目实现地图的初始化非常简单&#xff0c;只需要一帧&#xff08;左右目图像&#xff09;即可完成初始化。 行特征点统计。考虑用图像金字塔尺度作为偏移量&#xff0c;在当前点上下正负偏移量&#xff08;r)内的纵坐标值都认为是匹配点可能存在…

centos7 nginx1.18.0离线升级至1.25.1

centos7 nginx1.18.0离线升级至1.25.1 项目背景 系统&#xff1a;centos 7 nginx版本&#xff1a; 1.18.0 最近护网行动查出来 有关Nginx的几个安全漏洞&#xff0c;解决方案只需要更新Nginx版本到最新即可。 Nginx升级过程 1. 下载新版本nginx 下载地址&#xff1a;https:…

【0.2】lubancat鲁班猫4远程ubuntu22.04.2 无需任何安装

环境 lubancat4鲁班猫4 (4G0)不带emmc 系统镜像ubuntu-22.04.2-desktop-arm64-lubancat-4.img 网络环境:有线网络与本win10电脑同意环境 操作步骤ubuntu正常开机登陆用户&#xff0c;连接好网络进入设置>网络查看设备当前局域网IP 如192.168.199.159进入设置>共享>远程…

MAUI+Blazor:隐藏标题栏和问题

文章目录 前言相关文章代码问题有必要解决吗&#xff1f; 前言 最近在研究MAUIBlazor开发&#xff0c;发现一个问题&#xff0c;原生的的标题栏实在是太丑了。 相关文章 MAUI桌面端标题栏设置和窗口调整 MAUI Windows How to completely hide the TitleBar? #15142 MAUI …