Ниже приведены обновления, внесенные в эти области отображения:
- Изменение размера действий и отображений
- Размеры дисплея и соотношения сторон
- Политики отображения
- Настройки окна отображения
- Статические идентификаторы отображения
- Использование более двух дисплеев
- Фокусировка на каждом дисплее
Изменение размера действий и дисплеев
Чтобы указать, что приложение может не поддерживать многооконный режим или изменение размера, в активностях используется атрибут resizeableActivity=false
. При изменении размера активностей приложения сталкиваются с такими распространёнными проблемами:
- Конфигурация действия может отличаться от конфигурации приложения или другого невизуального компонента. Распространенной ошибкой является считывание показателей отображения из контекста приложения. Возвращаемые значения не будут соответствовать показателям видимой области, в которой отображается действие.
- Действие может не обрабатывать изменение размера и вызывать сбой, отображать искаженный пользовательский интерфейс или терять состояние из-за перезапуска без сохранения состояния экземпляра.
- Приложение может попытаться использовать абсолютные координаты ввода (вместо координат относительно положения окна), что может привести к нарушению ввода в многооконном режиме.
В 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
(в системном пользовательском интерфейсе) получает обратный вызов для отображения кнопки перезапуска процесса.
Размеры дисплея и соотношения сторон
Android 10 поддерживает новые соотношения сторон — от высоких для длинных и тонких экранов до 1:1. Приложения могут определять значения ApplicationInfo#maxAspectRatio
и ApplicationInfo#minAspectRatio
для поддерживаемых ими экранов.
Рисунок 1. Примеры соотношений сторон приложений, поддерживаемых в Android 10
Реализации устройств могут иметь вторичные дисплеи с размерами и разрешениями меньше, чем требуется для Android 9, и ниже (минимум 2,5 дюйма в ширину или высоту, минимум 320 DP для smallestScreenWidth
), но там могут размещаться только те действия, которые поддерживают эти маленькие дисплеи.
Приложения могут включить этот параметр, указав минимальный поддерживаемый размер, который меньше или равен целевому размеру экрана. Для этого используйте атрибуты макета активности android:minHeight
и android:minWidth
в файле AndroidManifest.
Политики отображения
Android 10 разделяет и перемещает некоторые политики отображения из реализации WindowManagerPolicy
по умолчанию в PhoneWindowManager
в классы отображения для каждого дисплея, например:
- Состояние отображения и поворот
- Некоторые ключи и отслеживание событий движения
- Системный интерфейс и окна оформления
В Android 9 (и более ранних версиях) класс PhoneWindowManager
управлял политиками отображения, состоянием и настройками, поворотом, отслеживанием рамки окна оформления и многим другим. В Android 10 большая часть этого перенесена в класс DisplayPolicy
, за исключением отслеживания поворота, которое было перемещено в DisplayRotation
.
Настройки окна отображения
В Android 10 настраиваемые параметры окон для каждого дисплея были расширены и теперь включают в себя:
- Режим отображения окон по умолчанию
- Значения пересканирования
- Ротация пользователя и режим ротации
- Принудительный размер, плотность и режим масштабирования
- Режим удаления контента (при удалении дисплея)
- Поддержка системных декораций и IME
Класс DisplayWindowSettings
содержит настройки для этих параметров. Они сохраняются на диске в разделе /data
в файле display_settings.xml
при каждом изменении параметра. Подробнее см. в разделах DisplayWindowSettings.AtomicFileStorage
и DisplayWindowSettings#writeSettings()
. Производители устройств могут указывать значения по умолчанию в display_settings.xml
для конфигурации устройства. Однако, поскольку файл хранится в разделе /data
, для восстановления файла, стертого при стирании, может потребоваться дополнительная логика.
По умолчанию Android 10 использует DisplayInfo#uniqueId
в качестве идентификатора дисплея при сохранении настроек. uniqueId
должно быть указано для всех дисплеев. Кроме того, оно стабильно для физических и сетевых дисплеев. В качестве идентификатора также можно использовать порт физического дисплея, который можно задать в DisplayWindowSettings#mIdentifier
. При каждой записи все настройки сохраняются, поэтому можно безопасно обновлять ключ, используемый для записи дисплея в хранилище. Подробнее см. в разделе Статические идентификаторы дисплеев .
Настройки сохраняются в каталоге /data
по историческим причинам. Изначально они использовались для сохранения пользовательских настроек, таких как поворот экрана.
Статические идентификаторы отображения
В Android 9 (и более ранних версиях) фреймворк не предоставлял стабильных идентификаторов для дисплеев. При добавлении дисплея в систему для него генерировался Display#mDisplayId
или DisplayInfo#displayId
путём увеличения статического счётчика. Если система добавляла и удаляла один и тот же дисплей, получался другой идентификатор.
Если на устройстве при загрузке было доступно несколько дисплеев, им можно было назначать разные идентификаторы в зависимости от времени загрузки. Хотя в Android 9 (и более ранних версиях) был включен DisplayInfo#uniqueId
, он не содержал достаточной информации для различения дисплеев, поскольку физические дисплеи идентифицировались как local:0
или local: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
содержит стабильный идентификатор дисплея (такой же, как в uniqueId
) и может быть создан с помощью DisplayAddress#fromPhysicalDisplayId()
.
Android 10 также предоставляет удобный метод для получения информации о порте ( Physical#getPort()
). Этот метод можно использовать в фреймворке для статической идентификации дисплеев. Например, он используется в DisplayWindowSettings
. DisplayAddress.Network
содержит MAC-адрес и может быть создан с помощью DisplayAddress#fromMacAddress()
.
Эти дополнения позволяют производителям устройств идентифицировать дисплеи в статических многодисплейных конфигурациях и настраивать различные системные параметры и функции, используя статические идентификаторы дисплеев, такие как порты для физических дисплеев. Эти методы скрыты и предназначены только для использования в рамках system_server
.
При наличии идентификатора дисплея HWC (который может быть непрозрачным и не всегда стабильным) этот метод возвращает 8-битный номер порта (зависящий от платформы), идентифицирующий физический разъём для вывода изображения на дисплей, а также блок данных EDID дисплея. SurfaceFlinger извлекает из EDID информацию о производителе или модели для генерации стабильных 64-битных идентификаторов дисплеев, предоставляемых фреймворку. Если этот метод не поддерживается или возникает ошибка, 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
предполагали наличие максимум двух физических дисплеев с жестко запрограммированными идентификаторами 0 и 1.
Начиная с Android 10, SurfaceFlinger может использовать API Hardware Composer (HWC) для генерации стабильных идентификаторов дисплеев, что позволяет управлять произвольным количеством физических дисплеев. Подробнее см. в разделе Статические идентификаторы дисплеев .
Фреймворк может искать токен IBinder
для физического дисплея через SurfaceControl#getPhysicalDisplayToken
после получения 64-битного идентификатора дисплея из SurfaceControl#getPhysicalDisplayIds
или из события горячего подключения DisplayEventReceiver
.
В Android 10 (и ниже) основной внутренний дисплей имеет TYPE_INTERNAL
, а все дополнительные дисплеи помечаются как TYPE_EXTERNAL
независимо от типа подключения. Таким образом, дополнительные внутренние дисплеи рассматриваются как внешние. В качестве обходного решения специфичный для устройства код может делать предположения о DisplayAddress.Physical#getPort
если известен HWC и логика выделения портов предсказуема.
Это ограничение снято в Android 11 (и выше).
- В Android 11 первый дисплей, указанный при загрузке, является основным. Тип подключения (внутренний или внешний) не имеет значения. Тем не менее, основной дисплей по-прежнему невозможно отключить, и на практике это должен быть внутренний дисплей. Обратите внимание, что некоторые складные телефоны имеют несколько внутренних дисплеев.
- Вторичные дисплеи правильно классифицируются как
Display.TYPE_INTERNAL
илиDisplay.TYPE_EXTERNAL
(ранее известные какDisplay.TYPE_BUILT_IN
иDisplay.TYPE_HDMI
соответственно) в зависимости от типа их подключения.
Выполнение
В Android 9 и более ранних версиях дисплеи идентифицируются 32-битными идентификаторами, где 0 — внутренний дисплей, 1 — внешний дисплей, [2, INT32_MAX]
— виртуальные дисплеи HWC, а -1 представляет недопустимый дисплей или виртуальный дисплей без HWC.
Начиная с Android 10, дисплеям присваиваются стабильные и постоянные идентификаторы, что позволяет SurfaceFlinger и DisplayManagerService
отслеживать более двух дисплеев и распознавать ранее просмотренные дисплеи. Если HWC поддерживает IComposerClient.getDisplayIdentificationData
и предоставляет идентификационные данные дисплея, SurfaceFlinger анализирует структуру EDID и выделяет стабильные 64-битные идентификаторы для физических и виртуальных дисплеев HWC. Идентификаторы выражаются с помощью типа параметра, где значение null соответствует недопустимому дисплею или виртуальному дисплею, отличному от 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::mFocusedWindowHandlesByDisplay
и InputDispatcher::setFocusedDisplay()
. Приложения с фокусом также обновляются отдельно в InputManagerService через NativeInputManager::setFocusedApplication()
.
В WindowManager
окна с фокусом также отслеживаются отдельно. См. DisplayContent#mCurrentFocus
и DisplayContent#mFocusedApp
и их применение. Соответствующие методы отслеживания и обновления фокуса перенесены из WindowManagerService
в DisplayContent
.