Modifier la valeur des ressources d'une application au moment de l'exécution

Une superposition de ressources d'exécution (RRO) est un package qui modifie les valeurs de ressources d'un package cible au moment de l'exécution. Par exemple, une application installée sur l'image système peut modifier son comportement en fonction de la valeur d'une ressource. Plutôt que de coder en dur la valeur de la ressource au moment de la compilation, un RRO installé sur une autre partition peut modifier les valeurs des ressources de l'application au moment de l'exécution.

Les RRO peuvent être activés ou désactivés. Vous pouvez définir de manière programmatique l'état d'activation/de désactivation pour activer ou désactiver la possibilité d'un RRO de modifier les valeurs de ressources. Les RRO sont désactivés par défaut (toutefois, les RRO statiques sont activés par défaut).

Ressources superposées

Les superpositions fonctionnent en mappant les ressources définies dans le package de superposition aux ressources définies dans le package cible. Lorsqu'une application tente de résoudre la valeur d'une ressource dans le package cible, la valeur de la ressource de superposition à laquelle la ressource cible est mappée est renvoyée à la place.

Configurer le fichier manifeste

Un package est considéré comme un package RRO s'il contient une balise <overlay> en tant qu'enfant de la balise <manifest>.

  • La valeur de l'attribut android:targetPackage obligatoire spécifie le nom du package que le RRO prévoit de superposer.

  • La valeur de l'attribut android:targetName facultatif spécifie le nom du sous-ensemble de ressources superposables du package cible que le RRO prévoit de superposer. Si la cible ne définit pas d'ensemble de ressources superposable, cet attribut ne doit pas être présent.

Le code suivant montre un exemple de superposition AndroidManifest.xml.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:targetName="OverlayableResources"/>
</manifest>

Les superpositions ne peuvent pas superposer de code. Elles ne peuvent donc pas comporter de fichiers DEX. De plus, l'attribut android:hasCode de la balise <application> dans le fichier manifeste doit être défini sur false.

Définir le mappage des ressources

Sous Android 11 ou version ultérieure, le mécanisme recommandé pour définir la carte des ressources de superposition consiste à créer un fichier dans le répertoire res/xml du package de superposition, à énumérer les ressources cibles à superposer et leurs valeurs de remplacement, puis à définir la valeur de l'attribut android:resourcesMap de la balise manifeste <overlay> sur une référence au fichier de mappage des ressources.

Le code suivant montre un exemple de fichier res/xml/overlays.xml.

<?xml version="1.0" encoding="utf-8"?>
<overlay xmlns:android="http://schemas.android.com/apk/res/android" >
    <!-- Overlays string/config1 and string/config2 with the same resource. -->
    <item target="string/config1" value="@string/overlay1" />
    <item target="string/config2" value="@string/overlay1" />

    <!-- Overlays string/config3 with the string "yes". -->
    <item target="string/config3" value="@android:string/yes" />

    <!-- Overlays string/config4 with the string "Hardcoded string". -->
    <item target="string/config4" value="Hardcoded string" />

    <!-- Overlays integer/config5 with the integer "42". -->
    <item target="integer/config5" value="42" />
</overlay>

Le code suivant illustre un exemple de fichier manifeste de superposition.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:targetName="OverlayableResources"
                   android:resourcesMap="@xml/overlays"/>
</manifest>

Créer le package

Android 11 ou version ultérieure est compatible avec une règle de compilation Soong pour les superpositions qui empêche Android Asset Packaging Tool 2 (AAPT2) de tenter de doubler les configurations de ressources ayant la même valeur (--no-resource-deduping) et de supprimer des ressources sans configuration par défaut (--no-resource-removal). Le code suivant montre un exemple de fichier Android.bp.

runtime_resource_overlay {
    name: "ExampleOverlay",
    sdk_version: "current",
}

Résoudre les ressources

Si une ressource cible ou une ressource de superposition comporte plusieurs configurations définies pour la ressource interrogée, l'environnement d'exécution des ressources renvoie la valeur de la configuration qui correspond le mieux à la configuration de l'appareil. Pour déterminer la configuration qui correspond le mieux, fusionnez l'ensemble des configurations de ressources superposées avec l'ensemble des configurations de ressources cibles, puis suivez le flux standard de résolution des ressources. Pour en savoir plus, consultez Comment Android trouve la ressource la plus pertinente.

Par exemple, si une superposition définit une valeur pour la configuration drawable-en et que la cible définit une valeur pour drawable-en-port, drawable-en-port correspond mieux. La valeur de la configuration cible drawable-en-port est donc choisie au moment de l'exécution. Pour superposer toutes les configurations drawable-en, la superposition doit définir une valeur pour chaque configuration drawable-en définie par la cible.

Les superpositions peuvent faire référence à leurs propres ressources, avec des comportements différents entre les versions d'Android.

  • Sous Android 11 ou version ultérieure, chaque superposition dispose de son propre espace d'ID de ressource réservé qui ne se chevauche pas avec l'espace d'ID de ressource cible ni avec d'autres espaces d'ID de ressource de superposition. Les superpositions qui font référence à leurs propres ressources fonctionnent donc comme prévu.

  • Sous Android 10 ou version antérieure, les superpositions et les packages cibles partagent le même espace d'ID de ressource, ce qui peut entraîner des collisions et un comportement inattendu lorsqu'ils tentent de référencer leurs propres ressources à l'aide de la syntaxe @type/name.

Activer/Désactiver les superpositions

Utilisez l'API OverlayManager pour activer et désactiver les superpositions modifiables (récupérez l'interface de l'API à l'aide de Context#getSystemService(Context.OVERLAY_SERVICE)). Une superposition ne peut être activée que par le package qu'elle cible ou par un package disposant de l'autorisation android.permission.CHANGE_OVERLAY_PACKAGES. Lorsqu'une superposition est activée ou désactivée, les événements de modification de la configuration se propagent au package cible et les activités cibles sont redémarrées.

Limiter les ressources superposables

Dans Android 10 ou version ultérieure, la balise XML <overlayable> expose un ensemble de ressources que les RRO sont autorisés à superposer. Dans l'exemple de fichier res/values/overlayable.xml suivant, string/foo et integer/bar sont des ressources utilisées pour thématiser l'apparence de l'appareil. Pour superposer ces ressources, une superposition doit cibler explicitement la collection de ressources superposées par leur nom.

<!-- The collection of resources for theming the appearance of the device -->
<overlayable name="ThemeResources">
       <policy type="public">
               <item type="string" name="foo/" />
               <item type="integer" name="bar/" />
       </policy>
       ...
</overlayable>

Un APK peut définir plusieurs balises <overlayable>, mais chaque balise doit avoir un nom unique dans le package. Par exemple :

  • Il est possible que deux packages différents définissent tous les deux <overlayable name="foo">.

  • Il n'est pas possible qu'un seul APK contienne deux blocs <overlayable name="foo">.

Le code suivant montre un exemple de superposition dans le fichier AndroidManifest.xml.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.my.theme.overlay">
       <application android:hasCode="false" />
       <!-- This overlay will override the ThemeResources resources -->
       <overlay android:targetPackage="android" android:targetName="ThemeResources">
</manifest>

Lorsqu'une application définit une balise <overlayable>, les superpositions ciblant cette application:

  • Vous devez spécifier targetName.

  • Seules les ressources répertoriées dans la balise <overlayable> peuvent se superposer.

  • Ne peut cibler qu'un seul nom <overlayable>.

Vous ne pouvez pas activer une superposition ciblant un package qui expose des ressources superposables, mais qui n'utilise pas android:targetName pour cibler une balise <overlayable> spécifique.

Règles de restriction

Utilisez le tag <policy> pour appliquer des restrictions sur les ressources superposables. L'attribut type indique les règles qu'une superposition doit respecter pour remplacer les ressources incluses. Les types acceptés sont les suivants :

  • public. Toute superposition peut remplacer la ressource.
  • system. Toute superposition sur la partition système peut remplacer les ressources.
  • vendor. Toute superposition sur la partition du fournisseur peut remplacer les ressources.
  • product. Toute superposition sur la partition de produits peut remplacer les ressources.
  • oem. Toute superposition sur la partition OEM peut remplacer les ressources.
  • odm. Toute superposition sur la partition odm peut remplacer les ressources.
  • signature. Toute superposition signée avec la même signature que l'APK cible peut remplacer les ressources.
  • actor. Toute superposition signée avec la même signature que l'APK actor peut remplacer les ressources. L'acteur est déclaré dans la balise named-actor dans la configuration système.
  • config_signature. Toute superposition signée avec la même signature que l'APK overlay-config peut remplacer les ressources. La configuration de superposition est déclarée dans la balise overlay-config-signature dans la configuration système.

Le code suivant montre un exemple de balise <policy> dans le fichier res/values/overlayable.xml.

<overlayable name="ThemeResources">
   <policy type="vendor" >
       <item type="string" name="foo" />
   </policy>
   <policy type="product|signature"  >
       <item type="string" name="bar" />
       <item type="string" name="baz" />
   </policy>
</overlayable>

Pour spécifier plusieurs règles, utilisez des barres verticales (|) comme caractères de séparation. Lorsque plusieurs règles sont spécifiées, une superposition ne doit répondre qu'à une seule règle pour remplacer les ressources répertoriées dans la balise <policy>.

Configurer les superpositions

Android propose différents mécanismes pour configurer la mutabilité, l'état par défaut et la priorité des superpositions en fonction de la version d'Android.

  • Les appareils équipés d'Android 11 ou version ultérieure peuvent utiliser un fichier OverlayConfig (config.xml) au lieu d'attributs de fichier manifeste. L'utilisation d'un fichier de superposition est la méthode recommandée pour les superpositions.

  • Tous les appareils peuvent utiliser des attributs de fichier manifeste (android:isStatic et android:priority) pour configurer des RRO statiques.

Utiliser OverlayConfig

Dans Android 11 ou version ultérieure, vous pouvez utiliser OverlayConfig pour configurer la mutabilité, l'état par défaut et la priorité des superpositions. Pour configurer une superposition, créez ou modifiez le fichier situé dans partition/overlay/config/config.xml, où partition est la partition de la superposition à configurer. Pour être configuré, un calque doit se trouver dans le répertoire overlay/ de la partition dans laquelle il est configuré. Le code suivant illustre un exemple de fichier product/overlay/config/config.xml.

<config>
    <merge path="OEM-common-rros-config.xml" />
    <overlay package="com.oem.overlay.device" mutable="false" enabled="true" />
    <overlay package="com.oem.green.theme" enabled="true" />
</config>"

La balise <overlay> nécessite un attribut package qui indique le package de superposition en cours de configuration. L'attribut facultatif enabled détermine si la superposition est activée par défaut (false par défaut). L'attribut facultatif mutable détermine si la superposition est modifiable ou non et peut modifier son état d'activation par programmation au moment de l'exécution (par défaut : true). Les superpositions qui ne sont pas répertoriées dans un fichier de configuration sont modifiables et désactivées par défaut.

Priorité des superpositions

Lorsque plusieurs superpositions remplacent les mêmes ressources, l'ordre des superpositions est important. Une superposition a une priorité plus élevée que les superpositions dont les configurations précèdent sa propre configuration. L'ordre de priorité des superpositions dans différentes partitions (de la priorité la plus faible à la priorité la plus élevée) est le suivant.

  • system
  • vendor
  • odm
  • oem
  • product
  • system_ext

Fusionner des fichiers

L'utilisation de balises <merge> permet de fusionner d'autres fichiers de configuration à l'emplacement spécifié dans le fichier de configuration. L'attribut path de la balise représente le chemin du fichier à fusionner par rapport au répertoire contenant les fichiers de configuration de la superposition.

Utiliser des attributs de fichier manifeste/des RRO statiques

Dans Android 10 ou version antérieure, l'immuabilité et la priorité de la superposition sont configurées à l'aide des attributs de fichier manifeste suivants.

  • android:isStatic. Lorsque la valeur de cet attribut booléen est définie sur true, la superposition est activée par défaut et ne peut pas être désactivée, ce qui empêche sa désactivation.

  • android:priority : la valeur de cet attribut numérique (qui n'affecte que les superpositions statiques) configure la priorité de la superposition lorsque plusieurs superpositions statiques ciblent la même valeur de ressource. Plus le nombre est élevé, plus la priorité est élevée.

Le code suivant montre un exemple de AndroidManifest.xml.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:isStatic="true"
                   android:priority="5"/>
</manifest>

Modifications apportées à Android 11

Dans Android 11 ou version ultérieure, si un fichier de configuration se trouve dans partition/overlay/config/config.xml, les superpositions sont configurées à l'aide de ce fichier. android:isStatic et android:priority n'ont aucun effet sur les superpositions situées dans la partition. La définition d'un fichier de configuration de superposition dans n'importe quelle partition applique la priorité de la partition de superposition.

De plus, Android 11 ou version ultérieure supprime la possibilité d'utiliser des superpositions statiques pour affecter les valeurs des ressources lues lors de l'installation du package. Pour le cas d'utilisation courant de l'utilisation de superpositions statiques pour modifier la valeur des valeurs booléennes qui configurent l'état d'activation des composants, utilisez la balise SystemConfig <component-override> (nouveau dans Android 11).

Superpositions de débogage

Pour activer, désactiver et vider manuellement les superpositions, utilisez la commande shell du gestionnaire de superpositions suivante.

adb shell cmd overlay

OverlayManagerService utilise idmap2 pour mapper les ID de ressource du package cible aux ID de ressources du package de superposition. Les mappages d'ID générés sont stockés dans /data/resource-cache/. Si votre superposition ne fonctionne pas correctement, recherchez le fichier idmap correspondant à votre superposition dans /data/resource-cache/, puis exécutez la commande suivante.

adb shell idmap2 dump --idmap-path [file]

Cette commande affiche le mappage des ressources, comme illustré ci-dessous.

[target res id] - > [overlay res id] [resource name]
0x01040151 -> 0x01050001 string/config_dozeComponent
0x01040152 -> 0x01050002 string/config_dozeDoubleTapSensorType
0x01040153 -> 0x01050003 string/config_dozeLongPressSensorType