Compatibilité avec les écrans

Vous trouverez ci-dessous les mises à jour apportées à ces zones spécifiques à l'affichage :

Redimensionner les activités et les écrans

Pour indiquer qu'une application n'est peut-être pas compatible avec le mode multifenêtre ou le redimensionnement, les activités utilisent l'attribut resizeableActivity=false. Voici quelques problèmes courants rencontrés par les applications lorsque les activités sont redimensionnées :

  • Une activité peut avoir une configuration différente de l'application ou d'un autre composant non visuel. Une erreur courante consiste à lire les métriques d'affichage à partir du contexte de l'application. Les valeurs renvoyées ne seront pas ajustées aux métriques de la zone visible dans laquelle une activité est affichée.
  • Une activité peut ne pas gérer le redimensionnement et planter, afficher une interface utilisateur déformée ou perdre son état en raison d'un redémarrage sans enregistrement de l'état de l'instance.
  • Une application peut tenter d'utiliser des coordonnées d'entrée absolues (au lieu de celles relatives à la position de la fenêtre), ce qui peut interrompre l'entrée en mode multifenêtre.

Sous Android 7 (et versions ultérieures), une application peut être définie sur resizeableActivity=false pour toujours s'exécuter en mode plein écran. Dans ce cas, la plate-forme empêche les activités non redimensionnables de passer en mode Écran partagé. Si l'utilisateur tente d'appeler une activité non redimensionnable à partir du lanceur alors qu'il est déjà en mode Écran partagé, la plate-forme quitte ce mode et lance l'activité non redimensionnable en mode plein écran.

Les applications qui définissent explicitement cet attribut sur false dans le fichier manifeste ne doivent pas être lancées en mode multifenêtre, sauf si le mode de compatibilité est appliqué :

  • La même configuration est appliquée au processus, qui contient toutes les activités et tous les composants non liés à l'activité.
  • La configuration appliquée répond aux exigences du CDD pour les écrans compatibles avec les applications.

Sous Android 10, la plate-forme empêche toujours les activités non redimensionnables de passer en mode Écran partagé, mais elles peuvent être mises à l'échelle temporairement si l'activité a déclaré une orientation ou un format fixe. Sinon, l'activité est redimensionnée pour remplir tout l'écran, comme sous Android 9 et versions antérieures.

L'implémentation par défaut applique la règle suivante :

Lorsqu'une activité est déclarée incompatible avec le mode multifenêtre à l'aide de l'attribut android:resizeableActivity et qu'elle remplit l'une des conditions décrites ci-dessous, lorsque la configuration d'écran appliquée doit changer, l'activité et le processus sont enregistrés avec la configuration d'origine, et l'utilisateur dispose d'une option pour relancer le processus de l'application afin d'utiliser la configuration d'écran mise à jour.

  • Orientation fixe via l'application de android:screenOrientation
  • L'application a un format maximal ou minimal par défaut en ciblant le niveau d'API ou déclare explicitement le format.

Cette figure affiche une activité non redimensionnable avec un format déclaré. Lorsque l'appareil est plié, la fenêtre est réduite pour s'adapter à la zone tout en conservant le format à l'aide du letterbox approprié. De plus, une option de redémarrage de l'activité est proposée à l'utilisateur chaque fois que la zone d'affichage de l'activité est modifiée.

Lorsque l'appareil est déplié, la configuration, la taille et le format de l'activité ne changent pas, mais l'option de redémarrage de l'activité s'affiche.

Lorsque resizeableActivity n'est pas défini (ou est défini sur true), l'application est entièrement compatible avec le redimensionnement.

Implémentation

Une activité non redimensionnable avec une orientation ou un format fixe est appelée mode de compatibilité de taille (SCM) dans le code. La condition est définie dans ActivityRecord#shouldUseSizeCompatMode(). Lorsqu'une activité SCM est lancée, la configuration liée à l'écran (telle que la taille ou la densité) est corrigée dans la configuration de remplacement demandée. L'activité ne dépend donc plus de la configuration d'affichage actuelle.

Si l'activité SCM ne peut pas remplir tout l'écran, elle est alignée en haut et centrée horizontalement. Les limites de l'activité sont calculées par AppWindowToken#calculateCompatBoundsTransformation().

Lorsqu'une activité SCM utilise une configuration d'écran différente de celle de son conteneur (par exemple, l'écran est redimensionné ou l'activité est déplacée vers un autre écran), ActivityRecord#inSizeCompatMode() est défini sur "true" et SizeCompatModeActivityController (dans l'interface utilisateur du système) reçoit le rappel pour afficher le bouton de redémarrage du processus.

Tailles d'écran et formats

Android 10 est compatible avec de nouveaux formats, allant des formats élevés des écrans longs et fins aux formats 1:1. Les applications peuvent définir ApplicationInfo#maxAspectRatio et le ApplicationInfo#minAspectRatio de l'écran qu'elles sont capables de gérer.

Rapports d'applications dans Android 10

Figure 1. Exemple de formats d'application compatibles avec Android 10

Les implémentations d'appareils peuvent avoir des écrans secondaires dont la taille et la résolution sont inférieures à celles requises par Android 9 et versions antérieures (minimum de 6, 35 cm de largeur ou de hauteur, minimum de 320 DP pour smallestScreenWidth), mais seules les activités qui acceptent ces petits écrans peuvent y être placées.

Les applications peuvent accepter en déclarant une taille minimale compatible inférieure ou égale à la taille d'affichage cible. Pour ce faire, utilisez les attributs de mise en page de l'activité android:minHeight et android:minWidth dans AndroidManifest.

Règles d'affichage

Android 10 sépare et déplace certaines règles d'affichage de l'implémentation WindowManagerPolicy par défaut dans PhoneWindowManager vers des classes par écran, telles que :

  • État et rotation de l'écran
  • Suivi de certains événements de touche et de mouvement
  • UI du système et fenêtres de décoration

Sous Android 9 (et versions antérieures), la classe PhoneWindowManager gérait les règles d'affichage, l'état et les paramètres, la rotation, le suivi du cadre de la fenêtre de décoration, etc. Android 10 déplace la plupart de ces éléments vers la classe DisplayPolicy, à l'exception du suivi de la rotation, qui a été déplacé vers DisplayRotation.

Paramètres de la fenêtre d'affichage

Sous Android 10, le paramètre de fenêtrage configurable par écran a été étendu pour inclure les éléments suivants :

  • Mode de fenêtrage par défaut
  • Valeurs d'overscan
  • Rotation et mode de rotation de l'utilisateur
  • Taille, densité et mode de mise à l'échelle forcés
  • Mode de suppression de contenu (lorsque l'écran est supprimé)
  • Compatibilité avec les décorations système et l'IME

La classe DisplayWindowSettings contient les paramètres de ces options. Ils sont conservés sur le disque dans la partition /data dans display_settings.xml chaque fois qu'un paramètre est modifié. Pour en savoir plus, consultez DisplayWindowSettings.AtomicFileStorage et DisplayWindowSettings#writeSettings(). Les fabricants d'appareils peuvent fournir des valeurs par défaut dans display_settings.xml pour la configuration de leur appareil. Toutefois, comme le fichier est stocké dans /data, une logique supplémentaire peut être nécessaire pour le restaurer s'il est effacé par un effacement.

Par défaut, Android 10 utilise DisplayInfo#uniqueId comme identifiant pour un écran lors de la conservation des paramètres. uniqueId doit être renseigné pour tous les écrans. De plus, il est stable pour les écrans physiques et réseau. Il est également possible d'utiliser le port d'un écran physique comme identifiant, qui peut être défini dans DisplayWindowSettings#mIdentifier. Lors de chaque écriture, tous les paramètres sont écrits. Il est donc possible de mettre à jour la clé utilisée pour une entrée d'écran dans le stockage. Pour en savoir plus, consultez Identifiants d'affichage statiques.

Les paramètres sont conservés dans le répertoire /data pour des raisons historiques. À l'origine, ils étaient utilisés pour conserver les paramètres définis par l'utilisateur, tels que la rotation de l'écran.

Identifiants d'affichage statiques

Android 9 (et versions antérieures) ne fournissait pas d'identifiants stables pour les écrans dans le framework. Lorsqu'un écran était ajouté au système, Display#mDisplayId ou DisplayInfo#displayId était généré pour cet écran en incrémentant un compteur statique. Si le système ajoutait et supprimait le même écran, un ID différent était généré.

Si un appareil disposait de plusieurs écrans disponibles au démarrage, des identifiants différents pouvaient leur être attribués, en fonction du moment. Bien qu'Android 9 (et versions antérieures) inclue DisplayInfo#uniqueId, il ne contenait pas suffisamment d'informations pour faire la distinction entre les écrans, car les écrans physiques étaient identifiés comme local:0 ou local:1, pour représenter l'écran intégré et l'écran externe.

Android 10 modifie DisplayInfo#uniqueId pour ajouter un identifiant stable et faire la distinction entre les écrans locaux, réseau et virtuels.

Type d'affichage Format
Local
local:<stable-id>
Réseau
network:<mac-address>
Virtuel
virtual:<package-name-and-name>

En plus des mises à jour de uniqueId, DisplayInfo.address contient DisplayAddress, un identifiant d'écran stable lors des redémarrages. Sous Android 10, DisplayAddress est compatible avec les écrans physiques et réseau. DisplayAddress.Physical contient un ID d'écran stable (identique à celui de uniqueId) et peut être créé avec DisplayAddress#fromPhysicalDisplayId().

Android 10 fournit également une méthode pratique pour obtenir des informations sur le port (Physical#getPort()). Cette méthode peut être utilisée dans le framework pour identifier statiquement les écrans. Par exemple, elle est utilisée dans DisplayWindowSettings). DisplayAddress.Network contient l'adresse MAC et peut être créé avec DisplayAddress#fromMacAddress().

Ces ajouts permettent aux fabricants d'appareils d'identifier les écrans dans les configurations multi-écrans statiques et de configurer différents paramètres et fonctionnalités du système à l'aide d'identifiants d'écran statiques, tels que les ports des écrans physiques. Ces méthodes sont masquées et ne sont destinées à être utilisées que dans system_server.

Étant donné un ID d'écran HWC (qui peut être opaque et pas toujours stable), cette méthode renvoie le numéro de port 8 bits (spécifique à la plate-forme) qui identifie un connecteur physique pour la sortie d'écran, ainsi que le blob EDID de l'écran. SurfaceFlinger extrait les informations sur le fabricant ou le modèle de l'EDID pour générer les ID d'écran stables de 64 bits exposés au framework. Si cette méthode n'est pas compatible ou génère des erreurs, SurfaceFlinger revient au mode MD hérité, où DisplayInfo#address est nul et DisplayInfo#uniqueId est codé en dur, comme décrit ci-dessus.

Pour vérifier que cette fonctionnalité est compatible, exécutez la commande suivante :

$ 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"

Utiliser plus de deux écrans

Sous Android 9 (et versions antérieures), SurfaceFlinger et DisplayManagerService supposaient l'existence d'au maximum deux écrans physiques avec des ID codés en dur 0 et 1.

À partir d'Android 10, SurfaceFlinger peut exploiter une API Hardware Composer (HWC) pour générer des ID d'écran stables, ce qui lui permet de gérer un nombre arbitraire d'écrans physiques. Pour en savoir plus, consultez Identifiants d'affichage statiques.

Le framework peut rechercher le jeton IBinder pour un écran physique via SurfaceControl#getPhysicalDisplayToken après avoir obtenu l'ID d'écran 64 bits à partir de SurfaceControl#getPhysicalDisplayIds ou d'un événement hotplug DisplayEventReceiver.

Sous Android 10 (et versions antérieures), l'écran interne principal est TYPE_INTERNAL et tous les écrans secondaires sont signalés comme TYPE_EXTERNAL, quel que soit le type de connexion. Par conséquent, les écrans internes supplémentaires sont traités comme externes. Pour contourner ce problème, le code spécifique à l'appareil peut faire des hypothèses sur DisplayAddress.Physical#getPort si le HWC est connu et que la logique d'allocation de port est prévisible.

Cette limitation est supprimée sous Android 11 (et versions ultérieures).

  • Sous Android 11, le premier écran signalé au démarrage est le écran principal. Le type de connexion (interne ou externe) n'est pas pertinent. Toutefois, il reste vrai que l'écran principal ne peut pas être déconnecté et qu'il doit donc être un écran interne en pratique. Notez que certains téléphones pliables disposent de plusieurs écrans internes.
  • Les écrans secondaires sont correctement classés comme Display.TYPE_INTERNAL ou Display.TYPE_EXTERNAL (anciennement Display.TYPE_BUILT_IN et Display.TYPE_HDMI, respectivement) en fonction de leur type de connexion.

Implémentation

Sous Android 9 et versions antérieures, les écrans sont identifiés par des ID de 32 bits, où 0 correspond à l'écran interne, 1 à l'écran externe, [2, INT32_MAX] aux écrans virtuels HWC, et -1 à un écran non valide ou à un écran virtuel non HWC.

À partir d'Android 10, les écrans reçoivent des ID stables et persistants, ce qui permet à SurfaceFlinger et à DisplayManagerService de suivre plus de deux écrans et de reconnaître les écrans déjà vus. Si le HWC est compatible avec IComposerClient.getDisplayIdentificationData et fournit des données d'identification d'écran, SurfaceFlinger analyse la structure EDID et alloue des ID d'écran stables de 64 bits pour les écrans physiques et virtuels HWC. Les ID sont exprimés à l'aide d'un type d'option, où la valeur nulle représente un écran non valide ou un écran virtuel non HWC. Sans la compatibilité HWC, SurfaceFlinger revient au comportement hérité avec au maximum deux écrans physiques.

Focus par écran

Pour prendre en charge plusieurs sources d'entrée ciblant des écrans individuels en même temps, Android 10 peut être configuré pour prendre en charge plusieurs fenêtres sélectionnées, au maximum une par écran. Cela n'est destiné qu'à des types d'appareils spéciaux lorsque plusieurs utilisateurs interagissent avec le même appareil en même temps et utilisent différentes méthodes ou appareils d'entrée, comme Android Automotive.

Il est fortement recommandé de ne pas activer cette fonctionnalité pour les appareils classiques, y compris les appareils multi-écrans ou ceux utilisés pour des expériences de type ordinateur. Cela est principalement dû à un problème de sécurité qui peut amener les utilisateurs à se demander quelle fenêtre est sélectionnée.

Imaginez l'utilisateur qui saisit des informations sécurisées dans un champ de saisie de texte, par exemple en se connectant à une application bancaire ou en saisissant du texte contenant des informations sensibles. Une application malveillante peut créer un écran virtuel hors écran pour exécuter une activité, également avec un champ de saisie de texte. Les activités légitimes et malveillantes sont sélectionnées et affichent toutes deux un indicateur d'entrée actif (curseur clignotant).

Toutefois, comme l'entrée d'un clavier (matériel ou logiciel) n'est saisie que dans l'activité la plus haute (l'application lancée le plus récemment), une application malveillante peut intercepter l'entrée de l'utilisateur en créant un écran virtuel masqué, même lorsqu'elle utilise un clavier logiciel sur l'écran de l'appareil principal.

Utilisez com.android.internal.R.bool.config_perDisplayFocusEnabled pour définir le focus par écran.

Compatibilité

Problème : sous Android 9 et versions antérieures, au maximum une fenêtre du système est sélectionnée à la fois.

Solution : dans le cas rare où deux fenêtres du même processus seraient sélectionnées, le système ne fournit le focus qu'à la fenêtre la plus haute dans l'ordre Z. Cette restriction est supprimée pour les applications qui ciblent Android 10, à partir duquel elles devraient pouvoir prendre en charge plusieurs fenêtres sélectionnées simultanément.

Implémentation

WindowManagerService#mPerDisplayFocusEnabled contrôle la disponibilité de cette fonctionnalité. Dans ActivityManager, ActivityDisplay#getFocusedStack() est désormais utilisé à la place du suivi global dans une variable. ActivityDisplay#getFocusedStack() détermine le focus en fonction de l'ordre Z au lieu de mettre en cache la valeur. Ainsi, une seule source, WindowManager, doit suivre l'ordre Z des activités.

ActivityStackSupervisor#getTopDisplayFocusedStack() adopte une approche similaire dans les cas où la pile sélectionnée la plus haute du système doit être identifiée. Les piles sont parcourues de haut en bas, à la recherche de la première pile éligible.

InputDispatcher peut désormais avoir plusieurs fenêtres sélectionnées (une par écran). Si un événement d'entrée est spécifique à l'écran, il est envoyé à la fenêtre sélectionnée de l'écran correspondant. Sinon, il est envoyé à la fenêtre sélectionnée de l'écran sélectionné, qui est l'écran avec lequel l'utilisateur a interagi le plus récemment.

Consultez InputDispatcher::mFocusedWindowHandlesByDisplay et InputDispatcher::setFocusedDisplay(). Les applications sélectionnées sont également mises à jour séparément dans InputManagerService via NativeInputManager::setFocusedApplication().

Dans WindowManager, les fenêtres sélectionnées sont également suivies séparément. Consultez DisplayContent#mCurrentFocus et DisplayContent#mFocusedApp, ainsi que leurs utilisations respectives. Les méthodes de suivi et de mise à jour du focus associées ont été déplacées de WindowManagerService vers DisplayContent.