系統裝飾支持

下面提供了對這些特定於顯示的區域所做的更新:

系統裝飾品

Android 10 增加了對配置輔助顯示器以顯示某些系統裝飾(例如壁紙、導航欄和啟動器)的支持。默認情況下,主顯示屏顯示所有系統裝飾,輔助顯示屏顯示可選啟用的裝飾。對輸入法編輯器 (IME) 的支持可以與其他系統裝飾分開設置。

使用DisplayWindowSettings#setShouldShowSystemDecorsLocked()在特定顯示器上添加對系統裝飾的支持,或在/data/system/display_settings.xml中提供默認值。例如,請參閱顯示窗口設置

執行

DisplayWindowSettings#setShouldShowSystemDecorsLocked()也暴露在WindowManager#setShouldShowSystemDecors()中用於測試。觸發此方法以啟用系統裝飾不會添加以前丟失的裝飾窗口,或者如果它們以前存在則將其刪除。在大多數情況下,系統裝飾支持的更改只有在設備重啟後才能完全生效。

檢查 WindowManager 代碼庫中對系統裝飾的支持通常通過DisplayContent#supportsSystemDecorations()進行,而檢查外部服務(例如係統 UI 以檢查是否應顯示導航欄)使用WindowManager#shouldShowSystemDecors() 。要了解此設置控制的內容,請探索這些方法的調用點。

系統 UI 裝飾窗口

Android 10添加了對導航欄的系統裝飾窗口支持,因為導航欄對於在 Activity 和應用程序之間導航是必不可少的。默認情況下,導航欄顯示 Back 和 Home 啟示。僅當目標顯示器支持系統裝飾時才包含此項(請參閱DisplayWindowSettings )。

狀態欄是一個更複雜的系統窗口,因為它還包含通知陰影、快速設置和鎖定屏幕。在 Android 10 中,輔助顯示器不支持狀態欄。因此,通知、設置和完整的鍵盤保護僅在主顯示屏上可用。

輔助屏幕不支持概覽/最近的系統窗口。在 Android 10 中,AOSP 僅在默認顯示屏上顯示“最近”,並包含來自所有顯示屏的活動。從“最近”啟動時,默認情況下,輔助顯示器上的活動會被帶到該顯示器的最前面。這種方法存在一些已知問題,例如當應用出現在其他屏幕上時不會立即更新。

執行

要實現額外的系統 UI 功能,設備製造商應使用單個系統 UI 組件來監聽顯示器的添加/移除並呈現適當的內容。

支持多顯示器 (MD) 的系統 UI 組件應處理以下情況:

  • 啟動時多顯示器初始化
  • 運行時添加的顯示
  • 在運行時移除顯示

當系統 UI 檢測到在 WindowManager 之前添加了一個顯示時,它會創建一個競爭條件。這可以通過在添加顯示而不是訂閱 DisplayManager .DisplayListener事件時實現從 WindowManager 到系統 UI 的自定義回調來避免。有關參考實現,請參閱CommandQueue.Callbacks#onDisplayReady以獲得導航欄支持和WallpaperManagerInternal#onDisplayReady以獲得壁紙。

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

  • NavigationBarController類控制特定於導航欄的所有功能。
  • 要查看自定義導航欄,請參閱CarStatusBar
  • TYPE_NAVIGATION_BAR不再局限於單個實例,並且可以在每個顯示器中使用。
  • IWindowManager#hasNavigationBar()已更新為僅包含系統 UI 的displayId參數。

啟動器

在 Android 10 中,每個配置為支持系統裝飾的顯示器都有一個專用的主堆棧,用於默認情況下類型為WindowConfiguration#ACTIVITY_TYPE_HOME的啟動器活動。每個顯示使用一個單獨的啟動器活動實例。

圖 1. platform/development/samples/MultiDisplay的多顯示器啟動器示例

大多數現有的啟動器不支持多個實例,也沒有針對大屏幕尺寸進行優化。此外,通常期望在輔助/外部顯示器上獲得不同類型的體驗。為了為輔助屏幕提供專用 Activity,Android 10 在 Intent 過濾器中引入了SECONDARY_HOME類別。此活動的實例用於所有支持系統裝飾的顯示器,每個顯示器一個。

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

Activity 必須有一個不會阻止多個實例的啟動模式,並且應該能夠適應不同的屏幕尺寸。啟動模式不能是singleInstancesingleTask

執行

在 Android 10 中, RootActivityContainer#startHomeOnDisplay()會根據啟動主屏幕的顯示自動選擇所需的組件和意圖。 RootActivityContainer#resolveSecondaryHomeActivity()包含根據當前選擇的啟動器查找啟動器活動組件的邏輯,如果需要,可以使用系統默認值(參見ActivityTaskManagerService#getSecondaryHomeIntent() )。

安全限制

除了適用於輔助顯示器上的活動的限制之外,為了避免惡意應用程序創建啟用了系統裝飾的虛擬顯示器並從表面讀取用戶敏感信息的可能性,啟動器僅出現在系統擁有的虛擬顯示器上。啟動器不會在非系統虛擬顯示器上顯示內容。

壁紙

在 Android 10(及更高版本)中,輔助顯示器支持壁紙:

圖 2.內部(上)和外部顯示器(下)上的動態壁紙

開發人員可以通過在WallpaperInfo XML 定義中提供android:supportsMultipleDisplays="true"來聲明對壁紙功能的支持。壁紙開發人員還應使用WallpaperService.Engine#getDisplayContext()中的顯示上下文加載資源。

該框架為每個顯示器創建一個WallpaperService.Engine實例,因此每個引擎都有自己的表面和顯示上下文。開發人員需要確保每個引擎可以獨立繪製,以不同的幀速率,尊重 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實例。

安全限制

該系統不會在它不擁有的虛擬顯示器上顯示壁紙。這是出於安全考慮,惡意應用程序可能會創建一個啟用了系統裝飾支持的虛擬顯示器,並從表面讀取用戶敏感信息(例如個人照片)。

執行

在 Android 10 中, IWallpaperConnection#attachEngine()IWallpaperService#attach()接口接受displayId參數來創建每個顯示器的連接。 WallpaperManagerService.DisplayConnector封裝了每個顯示器的壁紙引擎和連接。在 WindowManager 中,在構造時為每個DisplayContent對象創建牆紙控制器,而不是為所有顯示器創建一個WallpaperController控制器。

一些公共的WallpaperManager方法實現(例如WallpaperManager#getDesiredMinimumWidth() )已更新為計算並為相應的顯示提供信息。添加了WallpaperInfo#supportsMultipleDisplays()和相應的資源屬性,以便應用程序開發人員可以報告哪些壁紙已準備好用於多個屏幕。

如果默認顯示器上顯示的壁紙服務不支持多顯示器,則係統在輔助顯示器上顯示默認壁紙。

圖 3.輔助顯示器的壁紙回退邏輯