Recommended practices

Apps for foldable and multi-screen devices

Generally, apps should not rely on static identifiers or logic that depends on some display IDs. In most cases, apps should resize and work on different displays and the system should control where to locate apps. For example, to build a new and unique experience for foldable devices and launch a special app on the external screen when the device is folded.

In this case, SystemUI (or another system component) should detect the fold, determine if it's appropriate to perform an action, and then launch the target activity and specify an external display ID as the launch target. Apps shouldn't detect this action or perform any action in response and then perform the launch on a specific display. In other words, do not assume that what works on one device will work on other devices. In short, device-specific code increases fragmentation.

Restrict access to displays

If the device configuration requires the restriction of access to one or more displays, the recommendation is to use the Display#FLAG_PRIVATE flag to designate such displays as private. Doing so restricts all but the owner from adding content to the display. Any attempt to launch an activity or to add a window by anyone but the owner results in a SecurityException. If the system owns the display, the system can add windows and launch activities.

In addition, entities placed on a display can always access that display. If the owner launches an activity on a display, then the activity can launch other activities on this display. As a result, the owner is responsible for restricting access and allowing trusted apps only.

In addition, more restrictions are added to virtual displays because any app can create one without making it visible to the user. If the virtual display isn't owned by the system, then only activities with allowEmbedded are permitted and the caller should have the ACTIVITY_EMBEDDING permission.

For more information, see:

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

To conditionally control activity launches, use LaunchParamsController, which intercepts all activity launches and allows a system component to modify the parameters used for launch. This is available in system_server.

Configure display windowing settings and system decorations

System decorations can be configured per display in DisplayWindowSettings. A device implementation can provide a default configuration in /data/system/display_settings.xml.

This value determines whether system decorations (launcher, wallpaper, navigation bar, and other decor windows) and the IME appear on a display. For details, see DisplayWindowSettings#shouldShowSystemDecorsLocked() and DisplayWindowSettings#shouldShowImeLocked().

To identify the display, use either a unique ID (this default uses DisplayInfo#uniqueId) or a physical port ID for hardware displays (see DisplayInfo#address).

For example, the following display config example enables system decorations and the IME on a simulated display:

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

In the example above, uniqueId is used for display identification in the name attribute, which for a simulated display is overlay:1. For a built-in display, a sample value may be "local:45354385242535243453". Another option is to use hardware port information and set identifier="1" to correspond to DisplayWindowSettings#IDENTIFIER_PORT and then update the name to use the "port:<port_id>" format:

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

For details, see Static display identifiers.

For more information, see:

Switch displays between mirroring and hosting tasks

In Android 17 and higher, DisplayManager uses the FLAG_ALLOWS_CONTENT_MODE_SWITCH flag to control whether a display switches between mirroring and hosting tasks at run time. By default, this flag is enabled for external displays and disabled for all other displays.

When FLAG_ALLOWS_CONTENT_MODE_SWITCH is present, DisplayManager monitors the android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY secure setting to determine whether to mirror or host tasks. While this is the default logic, OEMs can customize this behavior.

Display topology and pointer movement

In Android 17 and higher, display topology defines the relative positions of displays and restricts mouse pointer movement to the specific set of displays in the topology.

WindowManager decides to include a display in the topology and calls DisplayManagerInternal.onDisplayBelongToTopologyChanged. DisplayManager checks DisplayTopologyCoordinator.isDisplayAllowedInTopology before adding the display. By default, if local displays can host tasks, the system adds them.

If there are multiple public displays that can host tasks, the decision to include the default display is handled by the shouldIncludeDefaultDisplayInTopology boolean provider that is passed to DisplayTopologyCoordinator. If the default display is the only public display that can host tasks, it's always in the topology. In AOSP, the boolean provider returns true only if the default display supports desktop windowing or if the secure setting Settings.Secure.INCLUDE_DEFAULT_DISPLAY_IN_TOPOLOGY is true.

Apps query the current topology using DisplayManager.getDisplayTopology and react to changes to the topology by registering a listener with DisplayManager.registerTopologyListener.