顯示支持

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

調整活動和顯示的大小

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

  • 活動可以具有與應用程式或其他非視覺元件不同的配置。一個常見的錯誤是從應用程式上下文中讀取顯示指標。傳回的值不會根據顯示活動的可見區域指標進行調整。
  • Activity 可能無法處理調整大小和崩潰、顯示扭曲的 UI 或因重新啟動而不儲存實例狀態而遺失狀態。
  • 應用程式可能會嘗試使用絕對輸入座標(而不是相對於視窗位置的座標),這可能會破壞多個視窗中的輸入。

在 Android 7(及更高版本)中,應用程式可以設定resizeableActivity=false以始終以全螢幕模式運作。在這種情況下,平台會阻止不可調整大小的活動進入分割畫面狀態。如果使用者在已處於分割畫面模式時嘗試從啟動器呼叫不可調整大小的 Activity,則平台將退出分割畫面模式並以全螢幕模式啟動不可調整大小的 Activity。

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

  • 相同的配置應用於包含所有活動和非活動組件的流程。
  • 應用的配置滿足應用程式相容顯示器的 CDD 要求。

在 Android 10 中,平台仍然阻止不可調整大小的 Activity 進入分割畫面模式,但如果 Activity 聲明了固定方向或寬高比,則可以暫時縮放它們。如果不是,則 Activity 會調整大小以填滿整個螢幕,如 Android 9 及更低版本中那樣。

預設實作應用以下策略:

當一個 Activity 透過使用android:resizeableActivity屬性聲明與多視窗不相容,並且該 Activity 滿足下述條件之一時,當應用程式的螢幕配置必須更改時,該 Activity 和進程將使用原始配置保存並且使用者可以重新啟動應用程式進程以使用更新的螢幕配置。

  • 透過android:screenOrientation的應用程式固定方向
  • 應用程式透過定位 API 等級或明確聲明寬高比來具有預設的最大或最小寬高比

該圖顯示了具有聲明的寬高比的不可調整大小的活動。折疊設備時,視窗會縮小以適合該區域,同時使用適當的信箱保持縱橫比。此外,每次變更活動的顯示區域時,都會提供使用者重新啟動活動選項。

展開設備時,Activity 的配置、大小和縱橫比不會改變,但會顯示重新啟動 Activity 的選項。

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 要求的輔助顯示器或更低(寬度或高度最小為 2.5 英寸,最小smallestScreenWidth寬度最小為 320 DP),但只有選擇支援這些小顯示器的活動才可以放置在那裡。

應用程式可以透過聲明小於 oe 等於目標顯示尺寸的最小支援尺寸來選擇加入。使用 AndroidManifest 中的android:minHeightandroid:minWidth活動佈局屬性來執行此操作。

顯示政策

Android 10 將某些顯示策略從PhoneWindowManager中的預設WindowManagerPolicy實作分離出來並移動到每個顯示類,例如:

  • 顯示狀態和旋轉
  • 一些按鍵和運動事件跟踪
  • 系統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作為顯示器的識別碼。應為所有顯示器填滿uniqueId 。此外,它對於實體和網路顯示都很穩定。也可以使用實體顯示器的連接埠作為標識符,可以在DisplayWindowSettings#mIdentifier中設定。每次寫入時,都會寫入所有設置,因此可以安全地更新儲存中用於顯示條目的密鑰。有關詳細信息,請參閱靜態顯示標識符

由於歷史原因,設定保留在/data目錄中。最初,它們用於保留用戶設定的設置,例如顯示旋轉。

靜態顯示標識符

Android 9(及更低版本)並未為框架中的顯示提供穩定的識別碼。當顯示器新增至系統時,透過增加靜態計數器為該顯示器產生Display#mDisplayIdDisplayInfo#displayId 。如果系統新增和刪除相同的顯示器,則會產生不同的 ID。

如果裝置在啟動時有多個可用的顯示器,則可以根據時間為顯示器指派不同的識別碼。雖然 Android 9(及更早版本)包含DisplayInfo#uniqueId ,但它沒有包含足夠的資訊來區分顯示器,因為實體顯示器被標識為local:0local:1 ,以表示內建顯示器和外部顯示器。

Android 10 更改了DisplayInfo#uniqueId以新增穩定的識別碼並區分本機、網路和虛擬顯示。

顯示類型格式
當地的
local:<stable-id>
網路
network:<mac-address>
虛擬的
virtual:<package-name-and-name>

除了uniqueId的更新之外, DisplayInfo.address還包含DisplayAddress ,這是一個在重新啟動後保持穩定的顯示識別碼。在 Android 10 中, DisplayAddress支援實體和網路顯示。 DisplayAddress.Physical包含穩定的顯示 ID(與uniqueId相同),可以使用DisplayAddress#fromPhysicalDisplayId()建立。

Android 10 也提供了一種便捷的方法來取得連接埠資訊( Physical#getPort() )。此方法可以在框架中用於靜態識別顯示器。例如,它用於DisplayWindowSettings )。 DisplayAddress.Network包含 MAC 位址,可以使用DisplayAddress#fromMacAddress()建立。

這些新增功能使設備製造商能夠識別靜態多顯示器設定中的顯示器,並使用靜態顯示器標識符(例如實體顯示器的連接埠)配置不同的系統設定和功能。這些方法是隱藏的,僅供在system_server內使用。

給定 HWC 顯示 ID(可能不透明且不總是穩定),此方法返回(特定於平台的)8 位元連接埠號,該連接埠號碼標識用於顯示輸出的實體連接器以及顯示器的 EDID blob。 SurfaceFlinger 從 EDID 中提取製造商或型號信息,以產生向框架公開的穩定的 64 位元顯示 ID。如果不支援此方法或出現錯誤,SurfaceFlinger 將回退到舊版 MD 模式,其中DisplayInfo#address為 null,並且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,這使其能夠管理任意數量的實體顯示器。要了解更多信息,請參閱靜態顯示標識符

SurfaceControl#getPhysicalDisplayToken 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() 。焦點應用程式也會透過NativeInputManager::setFocusedApplication()在 InputManagerService 中單獨更新。

WindowManager中,焦點視窗也被單獨追蹤。請參閱DisplayContent#mCurrentFocusDisplayContent#mFocusedApp以及各自的用途。相關的焦點追蹤和更新方法已從WindowManagerService移至DisplayContent