Рекомендации

На этой странице приведены рекомендации по работе с несколькими дисплеями.

Приложения для складных устройств и устройств с несколькими экранами

Как правило, приложения не должны полагаться на статические идентификаторы или логику, которая зависит от некоторых идентификаторов дисплея. В большинстве случаев приложения должны изменять размер и работать на разных дисплеях, а система должна контролировать, где размещать приложения. Например, вы можете создать уникальный интерфейс для складных устройств и запускать специальное приложение на внешнем экране, когда устройство сложено.

В этом случае интерфейс системы или другой системный компонент должен обнаружить складывание, определить, нужно ли выполнить действие, а затем запустить целевое действие и указать идентификатор внешнего дисплея в качестве цели запуска. Приложения не должны обнаруживать это действие или выполнять какие-либо действия в ответ на него, а затем запускаться на определенном экране. Поэтому не думайте, что то, что работает на одном устройстве, будет работать и на других. Другими словами, код, предназначенный для определенных устройств, приводит к фрагментации.

Ограничить доступ к дисплеям

Если конфигурация устройства требует ограничить доступ к одному или нескольким дисплеям, рекомендуем использовать флаг Display#FLAG_PRIVATE, чтобы обозначить дисплей как частный. Это означает, что добавлять контент на экран может только владелец. Если кто-то другой попытается запустить действие или добавить окно, будет вызвано исключение SecurityException. Если дисплей принадлежит системе, она может добавлять окна и запускать действия.

Кроме того, объекты, размещенные на дисплее, всегда могут получить доступ к этому дисплею. Если владелец запускает действие на экране, то это действие может запускать другие действия на этом экране. Поэтому владелец устройства должен ограничить доступ и разрешить его только проверенным приложениям.

Кроме того, к виртуальным дисплеям применяются дополнительные ограничения, поскольку любое приложение может создать такой дисплей, не показывая его пользователю. Если виртуальный дисплей не принадлежит системе, разрешены только действия с allowEmbedded, а у вызывающего объекта должно быть разрешение ACTIVITY_EMBEDDING.

Дополнительная информация:

  • ActivityStackSupervisor#isCallerAllowedToLaunchOnDisplay
  • ActivityDisplay#isUidPresent
  • DisplayManagerService#isUidPresentOnDisplay

Чтобы условно управлять запуском действий, используйте LaunchParamsController, который перехватывает все запуски действий и позволяет системному компоненту изменять параметры, используемые для запуска. Эта функция доступна в system_server.

Как настроить параметры окон и системные элементы интерфейса

Системные элементы можно настроить для каждого экрана в DisplayWindowSettings. Реализация устройства может предоставлять конфигурацию по умолчанию в /data/system/display_settings.xml.

Это значение определяет, будут ли на экране показываться системные элементы оформления (запускатор, обои, панель навигации и другие окна оформления) и метод ввода. Подробнее о том, DisplayWindowSettings#shouldShowSystemDecorsLocked и DisplayWindowSettings#shouldShowImeLocked…

Чтобы идентифицировать дисплей, используйте уникальный идентификатор (по умолчанию используется DisplayInfo#uniqueId) или идентификатор физического порта для аппаратных дисплеев (см. DisplayInfo#address).

Например, в следующем примере конфигурации дисплея на симулированном дисплее включены системные украшения и IME:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<display-settings>
<config identifier="0" />
<display
  name="overlay:1"
  shouldShowSystemDecors="true"
  shouldShowIme="true" />
</display-settings>

В приведенном выше примере для идентификации дисплея в атрибуте name используется значение uniqueId, которое для имитируемого дисплея равно overlay:1. Для встроенного дисплея значение может быть таким: "local:45354385242535243453". Вы также можете использовать информацию о порте устройства и задать для свойства identifier="1" значение, соответствующее DisplayWindowSettings#IDENTIFIER_PORT, а затем изменить название, используя формат "port:<port_id>":

<?xmlversion='1.0' encoding='utf-8' standalone='yes' ?>
<display-settings>
<config identifier="1" />
<display
  name="port:12345"
  shouldShowSystemDecors="true"
  shouldShowIme="true" />
</display-settings>

Подробнее о статичных идентификаторах дисплея…

Дополнительная информация:

Как переключаться между режимами дублирования экрана и ведущего экрана

В Android 17 и более поздних версий DisplayManager использует флаг FLAG_ALLOWS_CONTENT_MODE_SWITCH, чтобы определить, будет ли дисплей переключаться между зеркальным отображением и размещением задач во время выполнения. По умолчанию этот флаг включен для внешних дисплеев и отключен для всех остальных.

Если присутствует FLAG_ALLOWS_CONTENT_MODE_SWITCH, DisplayManager отслеживает безопасный параметр android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY, чтобы определить, нужно ли зеркалировать или размещать задачи. Хотя это логика по умолчанию, OEM-производители могут настроить это поведение.

Показывать топологию и движение указателя

В Android 17 и более поздних версий топология дисплея определяет относительное позиционирование дисплеев и ограничивает перемещение указателя мыши определенным набором дисплеев в топологии.

WindowManager решает включить дисплей в топологию и вызывает DisplayManagerInternal.onDisplayBelongToTopologyChanged. DisplayManager проверяет DisplayTopologyCoordinator.isDisplayAllowedInTopology, прежде чем добавить дисплей. По умолчанию система добавляет локальные дисплеи, если они могут выполнять задачи.

Если есть несколько общедоступных дисплеев, на которых можно выполнять задачи, решение о том, включать ли дисплей по умолчанию, принимает поставщик логического значения shouldIncludeDefaultDisplayInTopology, который передается в DisplayTopologyCoordinator. Если экран по умолчанию – единственный общедоступный экран, на котором можно размещать задачи, он всегда присутствует в топологии. В AOSP поставщик логических значений возвращает true, только если дисплей по умолчанию поддерживает многооконный режим или если для защищенного параметра Settings.Secure.INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY задано значение true.

Приложения запрашивают текущую топологию с помощью DisplayManager.getDisplayTopology и реагируют на изменения топологии, регистрируя прослушиватель с помощью DisplayManager.registerTopologyListener.