Поддержка дисплея

Обновления, внесенные в эти области отображения, представлены ниже:

Изменение размеров действий и дисплеев

Чтобы указать, что приложение может не поддерживать многооконный режим или изменение размера, действия используют 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 экрана, с которыми они могут работать.

соотношение приложений в Android 10

Рисунок 1. Пример соотношений приложений, поддерживаемых в Android 10

Реализации устройств могут иметь вторичные дисплеи с размерами и разрешением меньше, чем требуется для Android 9, и ниже (минимум 2,5 дюйма в ширину или высоту, минимум 320 DP для smallestScreenWidth ), но могут быть разрешены только действия, которые поддерживают эти маленькие дисплеи. размещены там.

Приложения могут согласиться, объявив минимальный поддерживаемый размер, который меньше, чем oe, равный целевому размеру экрана. Для этого используйте атрибуты макета активности 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. Идентификаторы выражаются с использованием типа параметра, где нулевое значение представляет недопустимый дисплей или виртуальный дисплей без 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 .