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

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

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

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

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