多媒體廣告支援

以下是針對這些特定顯示區域所做的更新:

調整活動和螢幕大小

如要指出應用程式可能不支援多視窗模式或調整大小,請使用 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#maxAspectRatioApplicationInfo#minAspectRatio

Android 10 中的應用程式比例

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

裝置實作項目中的次要螢幕尺寸和解析度可能低於 Android 9 所需的尺寸,甚至低於 Android 9 所需的尺寸 (寬度或高度至少 2.5 吋,smallestScreenWidth 的最低 320 DP),但只有選擇支援這些小螢幕的活動才能放置。

應用程式可以選擇加入,方法是宣告最小支援尺寸,且該尺寸小於或等於目標顯示尺寸。方法是使用 AndroidManifest 中的 android:minHeightandroid:minWidth 活動版面配置屬性。

顯示政策

Android 10 會將特定顯示政策從 PhoneWindowManager 中的預設 WindowManagerPolicy 實作項目分離並移至個別顯示類別,例如:

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

在 Android 9 (以下版本) 中,PhoneWindowManager 類別會處理顯示政策、狀態和設定、旋轉、裝飾視窗框架追蹤等。Android 10 將其中大部分項目移至 DisplayPolicy 類別,旋轉追蹤功能除外,該功能已移至 DisplayRotation

顯示畫面視窗設定

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

  • 預設顯示視窗模式
  • 超掃值
  • 使用者旋轉和旋轉模式
  • 強制大小、密度和縮放模式
  • 移除內容模式 (當螢幕移除時)
  • 支援系統裝飾和輸入法編輯器

DisplayWindowSettings 類別包含這些選項的設定。每次設定變更時,這類值都會保留在 display_settings.xml 中的 /data 分區中。詳情請參閱 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 中相同),且可使用 DisplayAddress#fromPhysicalDisplayId() 建立。

Android 10 也提供方便的方法來取得連接埠資訊 (Physical#getPort())。這個方法可用於架構中,用於靜態識別螢幕。例如,用於 DisplayWindowSettingsDisplayAddress.Network 包含 MAC 位址,且可透過 DisplayAddress#fromMacAddress() 建立。

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

若是 HWC 顯示 ID (可能不透明且不一定穩定),此方法會傳回 (平台專屬的) 8 位元通訊埠編號,用於識別顯示輸出內容的實體連接器,以及螢幕的 EDID blob。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 可利用 Hardware Composer (HWC) API 產生穩定的顯示 ID,藉此管理任意數量的實體螢幕。詳情請參閱「靜態顯示 ID」。

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

在 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)。

強烈建議您不要為一般裝置啟用這項功能,包括多螢幕裝置或用於提供類似電腦體驗的裝置。這主要是因為使用者可能有安全疑慮,可能會誤以為哪個視窗含有輸入焦點。

假設使用者在文字輸入欄位中輸入機密資訊,例如登入銀行應用程式,或是輸入含有敏感資訊的文字。惡意應用程式可能會建立用於執行活動的虛擬螢幕外螢幕,以及文字輸入欄位。合法和惡意活動都會獲得焦點,並顯示有效的輸入指標 (閃爍的游標)。

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

使用 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::mFocusedWindowHandlesByDisplayInputDispatcher::setFocusedDisplay()。在 InputManagerService 中,系統也會透過 NativeInputManager::setFocusedApplication() 個別更新已聚焦的應用程式。

WindowManager 中,系統也會個別追蹤聚焦視窗。請參閱 DisplayContent#mCurrentFocusDisplayContent#mFocusedApp 以及個別用途。相關的焦點追蹤和更新方法已從 WindowManagerService 移至 DisplayContent