系统装饰支持

以下提供了对这些特定于显示区域的更新

系统装饰

Android 10 增加了对配置辅助显示设备以显示某些系统装饰(例如壁纸、导航栏和启动器)的支持。默认情况下,主显示设备显示所有系统装饰,而辅助显示设备显示选择性启用的系统装饰。输入法编辑器 (IME) 的支持可以与其他系统装饰分开设置。

使用 DisplayWindowSettings#setShouldShowSystemDecorsLocked() 在特定显示设备上添加对系统装饰的支持,或在 /data/system/display_settings.xml 中提供默认值。有关示例,请参阅显示窗口设置

实现

DisplayWindowSettings#setShouldShowSystemDecorsLocked() 方法也在 WindowManager#setShouldShowSystemDecors() 中公开以供测试。使用启用系统装饰的意图触发此方法不会添加先前缺失的装饰窗口,也不会移除先前存在的装饰窗口。在大多数情况下,系统装饰支持的更改只有在设备重启后才能完全生效。

在 WindowManager 代码库中检查系统装饰支持通常会通过 DisplayContent#supportsSystemDecorations(),而外部服务(例如 System UI 检查是否应显示导航栏)的检查则使用 WindowManager#shouldShowSystemDecors()。要了解此设置控制的内容,请浏览这些方法的调用点。

系统界面装饰窗口

Android 10 为导航栏添加了系统装饰窗口支持,因为导航栏对于在应用和 Activity 之间导航至关重要。默认情况下,导航栏显示“返回”和“主页”控件。只有当目标显示器支持系统装饰时(请参阅 DisplayWindowSettings),才会包含此功能。

状态栏是一个更复杂的系统窗口,因为它还包含通知栏、快速设置和锁屏界面。在 Android 10 中,辅助显示器不支持状态栏。因此,通知、设置和完整的锁屏界面仅在主显示器上可用。

辅助屏幕不支持 概览/最近任务 系统窗口。在 Android 10 中,AOSP 仅在默认显示器上显示最近任务,并包含来自所有显示器的 Activity。从最近任务启动时,默认情况下,辅助显示器上的 Activity 会被置于该显示器的前台。这种方法存在一些已知问题,例如,当应用出现在其他屏幕上时,不会立即更新。

实现

为了实现额外的 System UI 功能,设备制造商应使用单个 System UI 组件,该组件监听显示器的添加/移除并呈现适当的内容。

支持多显示器 (MD) 的 System UI 组件应处理以下情况:

  • 启动时的多个显示器初始化
  • 运行时添加显示器
  • 运行时移除显示器

当 System UI 在 WindowManager 之前检测到显示器的添加时,会创建竞争条件。可以通过实现从 WindowManager 到 System UI 的自定义回调来避免这种情况,以便在添加显示器时通知 System UI,而不是订阅 DisplayManager.DisplayListener 事件。有关参考实现,请参阅导航栏支持的 CommandQueue.Callbacks#onDisplayReady 和壁纸支持的 WallpaperManagerInternal#onDisplayReady

此外,Android 10 还提供以下更新:

  • NavigationBarController 类控制所有特定于导航栏的功能。
  • 要查看自定义导航栏,请参阅 CarStatusBar
  • TYPE_NAVIGATION_BAR 不再限制为单个实例,并且可以按显示器使用。
  • IWindowManager#hasNavigationBar() 已更新为包含仅供 System UI 使用的 displayId 参数。

启动器

在 Android 10 中,每个配置为支持系统装饰的显示器都有一个专用的主屏幕堆栈,用于类型为 WindowConfiguration#ACTIVITY_TYPE_HOME 的启动器 Activity(默认情况下)。每个显示器都使用一个单独的启动器 Activity 实例。

图 1. platform/development/samples/MultiDisplay 的多显示器启动器示例

大多数现有启动器都不支持多个实例,并且未针对大屏幕尺寸进行优化。此外,在辅助/外部显示器上通常期望获得不同的体验。为了为辅助屏幕提供专用的 Activity,Android 10 在 intent 过滤器中引入了 SECONDARY_HOME 类别。此 Activity 的实例用于所有支持系统装饰的显示器,每个显示器一个实例。

<activity>
    ...
    <intent-filter>
        <category android:name="android.intent.category.SECONDARY_HOME" />
        ...
    </intent-filter>
</activity>

该 Activity 必须具有不允许多个实例的启动模式,并且应适应不同的屏幕尺寸。启动模式不能为 singleInstancesingleTask

实现

在 Android 10 中,RootActivityContainer#startHomeOnDisplay() 会根据启动主屏幕的显示器自动选择所需的组件和 intent。RootActivityContainer#resolveSecondaryHomeActivity() 包含查找启动器 Activity 组件的逻辑,具体取决于当前选择的启动器,并且可以在需要时使用系统默认启动器(请参阅 ActivityTaskManagerService#getSecondaryHomeIntent())。

安全限制

除了适用于辅助显示器上的 Activity 的限制之外,为了避免恶意应用创建启用 系统装饰 的虚拟显示器并从 Surface 读取用户敏感信息的可能性,启动器仅在系统拥有的虚拟显示器上显示。启动器不会在非系统虚拟显示器上显示内容。

壁纸

在 Android 10(及更高版本)中,辅助显示器支持壁纸

图 2. 内部显示器(上图)和外部显示器(下图)上的动态壁纸

开发者可以通过在 WallpaperInfo XML 定义中提供 android:supportsMultipleDisplays="true" 来声明对壁纸功能的支持。还期望壁纸开发者在 WallpaperService.Engine#getDisplayContext() 中使用显示器上下文加载资源。

框架为每个显示器创建一个 WallpaperService.Engine 实例,因此每个引擎都有自己的 Surface 和显示器上下文。开发者需要确保每个引擎可以独立绘制,以不同的帧速率,并遵守 VSYNC。

为各个屏幕选择壁纸

Android 10 不提供直接的平台支持来为各个屏幕选择壁纸。为了实现这一点,需要一个稳定的显示器标识符来持久保存每个显示器的壁纸设置。Display#getDisplayId() 是动态的,因此无法保证物理显示器在重启后具有相同的 ID。

但是,Android 10 添加了 DisplayInfo.mAddress,其中包含物理显示器的稳定标识符,可以在未来用于完整实现。遗憾的是,现在为 Android 10 实现此逻辑为时已晚。建议的解决方案是:

  1. 使用 WallpaperManager API 设置壁纸。
  2. WallpaperManager 是从 Context 对象获得的,并且每个 Context 对象都具有有关相应显示器的信息 (Context#getDisplay()/getDisplayId())。因此,您可以从 WallpaperManager 实例获得 displayId,而无需添加新方法。
  3. 在框架端,使用从 Context 对象获得的 displayId,并将其映射到静态标识符(例如物理显示器的端口)。使用静态标识符来持久保存所选壁纸。

此解决方法使用现有的壁纸选择器实现。如果在特定显示器上打开它并使用正确的上下文,那么当它调用以设置壁纸时,系统可以自动识别显示器。

如果需要为当前显示器以外的显示器设置壁纸,则为目标显示器创建一个新的 Context 对象 (Context#createDisplayContext),并从该显示器获得 WallpaperManager 实例。

安全限制

系统不会在它不拥有的虚拟显示器上显示壁纸。这是出于安全考虑,因为恶意应用可能会创建一个启用系统装饰支持的虚拟显示器,并从 Surface 读取用户敏感信息(例如个人照片)。

实现

在 Android 10 中,IWallpaperConnection#attachEngine()IWallpaperService#attach() 接口接受 displayId 参数以创建每个显示器的连接。WallpaperManagerService.DisplayConnector 封装了每个显示器的壁纸引擎和连接。在 WindowManager 中,壁纸控制器是在构造时为每个 DisplayContent 对象创建的,而不是为所有显示器创建一个 WallpaperController

一些公共 WallpaperManager 方法实现(例如 WallpaperManager#getDesiredMinimumWidth())已更新为计算和提供相应显示器的信息。WallpaperInfo#supportsMultipleDisplays() 和相应的资源属性已添加,以便应用开发者可以报告哪些壁纸已准备好用于多个屏幕。

如果默认显示器上显示的壁纸服务不支持多显示器,则系统会在辅助显示器上显示默认壁纸。

图 3. 辅助显示器的壁纸回退逻辑