螢幕支援

以下是這些顯示器專屬區域的更新內容:

調整活動和螢幕大小

如要指出應用程式可能不支援多視窗模式或調整大小,活動會使用 resizeableActivity=false 屬性。應用程式在活動大小調整時遇到的常見問題包括:

  • 活動的設定可能與應用程式或其他非視覺化元件不同。常見的錯誤是從應用程式環境讀取螢幕指標。系統不會根據活動顯示的顯示區域指標調整回傳值。
  • 活動可能無法處理大小調整作業而當機、顯示扭曲的 UI,或因重新啟動時未儲存例項狀態而遺失狀態。
  • 應用程式可能會嘗試使用絕對輸入座標 (而非與視窗位置相關的座標),這可能會導致多視窗模式下的輸入中斷。

在 Android 7 以上版本中,應用程式可以設定為一律以全螢幕模式執行。resizeableActivity=false在這種情況下,平台會防止無法調整大小的活動進入分割畫面。如果使用者在分割畫面模式下,嘗試從啟動器叫用無法調整大小的活動,平台會退出分割畫面模式,並以全螢幕模式啟動無法調整大小的活動。

如果應用程式在資訊清單中將這項屬性明確設為 false,就不得在多視窗模式下啟動,除非套用相容模式:

  • 相同設定會套用至程序,其中包含所有活動和非活動元件。
  • 套用的設定符合應用程式相容螢幕的 CDD 要求。

在 Android 10 中,平台仍會禁止無法調整大小的活動進入分割畫面模式,但如果活動已宣告固定螢幕方向或長寬比,則可暫時縮放。如果沒有,活動會像在 Android 9 以下版本一樣,調整大小以填滿整個畫面。

預設實作方式會套用下列政策:

如果活動透過 android:resizeableActivity 屬性宣告與多視窗不相容,且符合下列其中一項條件,當套用的螢幕設定必須變更時,系統會儲存活動和程序,並保留原始設定,同時提供重新啟動應用程式程序的機制,讓使用者能套用更新後的螢幕設定。

  • 是否透過套用固定螢幕方向 android:screenOrientation
  • 應用程式指定 API 級別,或明確宣告長寬比,因此具有預設最大或最小長寬比

這張圖顯示無法調整大小的活動,且已宣告顯示比例。 摺疊裝置時,視窗會縮小以配合區域,並使用適當的上下黑邊維持顯示比例。此外,每當活動的顯示區域變更時,系統都會向使用者提供重新啟動活動的選項。

展開裝置時,活動的設定、大小和顯示比例不會變更,但會顯示重新啟動活動的選項。

如果未設定 resizeableActivity (或設為 true),應用程式會完全支援調整大小。

導入作業

如果活動無法調整大小,且螢幕方向或顯示比例固定,程式碼中會稱為「大小相容性模式」(SCM)。條件是在 ActivityRecord#shouldUseSizeCompatMode() 中定義。啟動 SCM 活動時,螢幕相關設定 (例如大小或密度) 會固定在要求的覆寫設定中,因此活動不再取決於目前的螢幕設定。

如果 SCM 活動無法填滿整個畫面,系統會將其置於畫面頂端並水平置中。活動界限是由 AppWindowToken#calculateCompatBoundsTransformation() 計算。

如果 SCM 活動使用的螢幕設定與容器不同 (例如顯示畫面大小調整,或活動移至其他顯示畫面),ActivityRecord#inSizeCompatMode() 為 true,且 SizeCompatModeActivityController (位於系統 UI 中) 會收到回呼,顯示程序重新啟動按鈕。

螢幕大小和顯示比例

Android 10 支援新的長寬比,從長而窄的螢幕高長寬比到 1:1 長寬比皆可。應用程式可以定義 ApplicationInfo#maxAspectRatio 和可處理的畫面 ApplicationInfo#minAspectRatio

Android 10 中的應用程式比例

圖 1. Android 10 支援的應用程式比例範例

裝置實作項目可使用尺寸和解析度小於 Android 9 要求的第二螢幕 (最小寬度或高度為 2.5 吋,smallestScreenWidth 最小為 320 DP),但只有選擇支援這些小型螢幕的活動才能放置在該處。

應用程式可以宣告小於或等於目標螢幕尺寸的最小支援尺寸,藉此選擇加入。如要這麼做,請在 AndroidManifest 中使用 android:minHeightandroid:minWidth 活動版面配置屬性。

顯示政策

Android 10 會將某些螢幕政策從預設的 WindowManagerPolicy 實作項目中分離出來,並移至每個螢幕的類別,例如:PhoneWindowManager

  • 顯示狀態和旋轉
  • 部分按鍵和動作事件追蹤
  • 系統 UI 和裝飾視窗

在 Android 9 (和更早版本) 中,PhoneWindowManager 類別會處理螢幕政策、狀態和設定、旋轉、裝飾視窗框架追蹤等。Android 10 會將大部分內容移至 DisplayPolicy 類別,但旋轉追蹤功能除外,該功能已移至 DisplayRotation

顯示視窗設定

在 Android 10 中,可設定的每個螢幕視窗設定已擴充,包括:

  • 預設顯示視窗模式
  • 過掃值
  • 使用者輪替和輪替模式
  • 強制大小、密度和縮放模式
  • 內容移除模式 (螢幕移除時)
  • 支援系統裝飾和 IME

DisplayWindowSettings 類別包含這些選項的設定。每次變更設定時,這些設定都會儲存在 /data 分割區的磁碟中。display_settings.xml詳情請參閱 DisplayWindowSettings.AtomicFileStorageDisplayWindowSettings#writeSettings()。裝置製造商可以在 display_settings.xml 中提供裝置設定的預設值。不過,由於檔案儲存在 /data 中,如果檔案遭到清除,可能需要額外的邏輯才能還原。

根據預設,Android 10 會使用 DisplayInfo#uniqueId 做為顯示器的 ID,以便保存設定。所有螢幕都應填入 uniqueId。此外,實體和網路螢幕顯示器也適用。您也可以使用實體螢幕的連接埠做為 ID,這可以在 DisplayWindowSettings#mIdentifier 中設定。每次寫入時,系統都會寫入所有設定,因此可以安全地更新用於儲存空間中顯示項目的金鑰。詳情請參閱「靜態螢幕 ID」。

基於歷史因素,設定會保留在 /data 目錄中。原本是用來保存使用者設定,例如螢幕旋轉。

靜態螢幕 ID

Android 9 以下版本未在架構中提供穩定的螢幕 ID。當顯示器新增至系統時,系統會遞增靜態計數器,為該顯示器產生 Display#mDisplayIdDisplayInfo#displayId。如果系統新增及移除相同的螢幕,就會產生不同的 ID。

如果裝置在啟動時有多個可用螢幕,系統可能會根據時間指派不同的 ID 給這些螢幕。雖然 Android 9 (和更早版本) 包含 DisplayInfo#uniqueId,但資訊不足以區分顯示器,因為實體顯示器會識別為 local:0local:1,代表內建和外部顯示器。

Android 10 變更 DisplayInfo#uniqueId,新增穩定 ID,並區分本機、網路和虛擬螢幕。

顯示螢幕類型 格式
本機
local:<stable-id>
網路
network:<mac-address>
虛擬
virtual:<package-name-and-name>

除了 uniqueId 的更新之外,DisplayInfo.address 還包含 DisplayAddress,這是重新啟動後仍維持不變的螢幕 ID。在 Android 10 中,DisplayAddress 支援實體和網路螢幕。DisplayAddress.Physical 包含穩定的顯示 ID (與 uniqueId 中的 ID 相同),且可使用 DisplayAddress#fromPhysicalDisplayId() 建立。

Android 10 也提供便利的方法來取得通訊埠資訊 (Physical#getPort())。這個方法可在框架中使用,以靜態方式識別螢幕。舉例來說,這項功能用於 DisplayWindowSettingsDisplayAddress.Network 包含 MAC 位址,可使用 DisplayAddress#fromMacAddress() 建立。

裝置製造商可透過這些新增項目,在靜態多螢幕設定中識別螢幕,並使用靜態螢幕 ID (例如實體螢幕的連接埠) 設定不同的系統設定和功能。這些方法會隱藏起來,僅供 system_server 內部使用。

這個方法會傳回 (平台專屬) 8 位元通訊埠編號,用於識別顯示器輸出的實體連接器,以及顯示器的 EDID Blob,前提是提供 HWC 顯示器 ID (可能不透明且不一定穩定)。SurfaceFlinger 會從 EDID 擷取製造商或型號資訊,產生穩定且向架構公開的 64 位元螢幕 ID。如果系統不支援這個方法或發生錯誤,SurfaceFlinger 會改用舊版 MD 模式,如上所述,此時 DisplayInfo#address 為空值,且 DisplayInfo#uniqueId 會經過硬式編碼。

如要確認是否支援這項功能,請執行:

$ 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"

使用超過兩個螢幕

在 Android 9 (以下版本) 中,SurfaceFlinger 和 DisplayManagerService 假設最多有兩個實體螢幕,且 ID 分別為 0 和 1。

從 Android 10 開始,SurfaceFlinger 可以運用硬體合成器 (HWC) API 生成穩定的螢幕 ID,藉此管理任意數量的實體螢幕。詳情請參閱「靜態顯示 ID」。

架構可透過 SurfaceControl#getPhysicalDisplayToken 查詢實體螢幕的 IBinder 權杖,方法是從 SurfaceControl#getPhysicalDisplayIdsDisplayEventReceiver 熱插拔事件取得 64 位元螢幕 ID。

在 Android 10 (和更低版本) 中,主要內部螢幕為 TYPE_INTERNAL,所有次要螢幕都會標示為 TYPE_EXTERNAL,無論連線類型為何。因此,額外的內部螢幕會視為外部螢幕。 做為因應措施,裝置專屬程式碼可以根據 HWC 是否已知,以及通訊埠分配邏輯是否可預測,做出假設。DisplayAddress.Physical#getPort

Android 11 以上版本已移除這項限制。

  • 在 Android 11 中,開機時回報的第一個螢幕是主要螢幕。連線類型 (內部或外部) 無關緊要。 不過,主要螢幕仍無法中斷連線,因此實際上必須是內部螢幕。請注意,部分摺疊手機有多個內部螢幕。
  • 系統會根據連線類型,將次要螢幕正確分類為 Display.TYPE_INTERNALDisplay.TYPE_EXTERNAL (舊稱 Display.TYPE_BUILT_INDisplay.TYPE_HDMI)。

導入作業

在 Android 9 以下版本中,螢幕會以 32 位元 ID 識別,其中 0 是內部螢幕、1 是外部螢幕、[2, INT32_MAX] 是 HWC 虛擬螢幕,而 -1 代表無效螢幕或非 HWC 虛擬螢幕。

從 Android 10 開始,螢幕會獲得穩定且持續的 ID,讓 SurfaceFlinger 和 DisplayManagerService 追蹤兩個以上的螢幕,並辨識先前看過的螢幕。如果 HWC 支援 IComposerClient.getDisplayIdentificationData 並提供螢幕識別資料,SurfaceFlinger 會剖析 EDID 結構,並為實體和 HWC 虛擬螢幕分配穩定的 64 位元螢幕 ID。ID 會以選項型別表示,其中空值代表無效的螢幕或非 HWC 虛擬螢幕。如果沒有 HWC 支援,SurfaceFlinger 會回復為舊版行為,最多支援兩個實體螢幕。

每個螢幕的焦點

如要支援同時以個別螢幕為目標的多個輸入來源,Android 10 可設定為支援多個焦點視窗,每個螢幕最多一個。這項功能僅適用於特殊類型的裝置,例如 Android Automotive,可讓多位使用者同時與同一部裝置互動,並使用不同的輸入方式或裝置。

強烈建議不要為一般裝置啟用這項功能,包括多螢幕裝置或用於類似桌機體驗的裝置。這主要是因為安全疑慮,可能會導致使用者不確定哪個視窗具有輸入焦點。

假設使用者在文字輸入欄位中輸入安全資訊,例如登入銀行應用程式,或輸入含有私密資訊的文字。惡意應用程式可能會建立虛擬螢幕外顯示器,藉此執行活動,包括文字輸入欄位。正當和惡意活動都會取得焦點,且都會顯示有效輸入指標 (閃爍的游標)。

不過,由於鍵盤 (硬體或軟體) 輸入內容只會進入最上層的 Activity (最近啟動的應用程式),因此惡意應用程式可以建立隱藏的虛擬螢幕,藉此擷取使用者輸入內容,即使是在主要裝置螢幕上使用螢幕鍵盤也一樣。

使用 com.android.internal.R.bool.config_perDisplayFocusEnabled 設定每個螢幕的焦點。

相容性

問題:在 Android 9 以下版本中,系統一次最多只能有一個視窗處於焦點狀態。

解決方案:在極少數情況下,如果同一個程序中的兩個視窗都會成為焦點,系統只會將焦點提供給 Z 順序較高的視窗。如果應用程式以 Android 10 為目標,這項限制就會移除,屆時應用程式應可支援同時聚焦多個視窗。

導入作業

WindowManagerService#mPerDisplayFocusEnabled 可控管這項功能是否可用。在 ActivityManager 中,現在會使用 ActivityDisplay#getFocusedStack(),而不是變數中的全域追蹤。ActivityDisplay#getFocusedStack() 根據 Z 順序決定焦點,而不是快取值。這樣一來,只有一個來源 (WindowManager) 需要追蹤活動的 Z 順序。

ActivityStackSupervisor#getTopDisplayFocusedStack() 採用類似方法,在系統中找出最上層的焦點堆疊。系統會從上到下遍歷堆疊,搜尋第一個符合資格的堆疊。

InputDispatcher 現在可以有多個焦點視窗 (每個螢幕一個)。如果輸入事件是特定螢幕專屬,系統會將事件分派至相應螢幕中的焦點視窗。否則,系統會將事件分派至焦點畫面中的焦點視窗,也就是使用者最近互動的畫面。

請參閱《InputDispatcher::mFocusedWindowHandlesByDisplay》和《InputDispatcher::setFocusedDisplay()》。InputManagerService 也會透過 NativeInputManager::setFocusedApplication() 分別更新焦點應用程式。

WindowManager 中,系統也會分別追蹤聚焦視窗。 請參閱《DisplayContent#mCurrentFocus》和《DisplayContent#mFocusedApp》,瞭解相關用途。相關的焦點追蹤和更新方法已從 WindowManagerService 移至 DisplayContent