Ниже приведены обновления, внесенные в эти области отображения:
- Изменение размера действий и дисплеев
- Размеры дисплея и соотношения сторон
- Политики отображения
- Настройки окна отображения
- Статические идентификаторы отображения
- Использование более двух дисплеев
- Фокусировка на каждом дисплее
Изменение размера действий и дисплеев
Чтобы указать, что приложение может не поддерживать многооконный режим или изменение размера, действия используют атрибут 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
.