Android protège les utilisateurs contre les applications malveillantes et offre une expérience d'interface utilisateur fiable. Le framework Activity Security comprend des règles et des restrictions de plate-forme. Ces règles et restrictions empêchent les interruptions indésirables de l'interface utilisateur, le piratage de tâches et d'autres menaces de sécurité. Ces menaces concernent le moment et la manière dont les composants de l'application s'affichent à l'écran. Un composant clé de ce framework empêche le démarrage d'activités en arrière-plan.
Restrictions concernant le lancement d'activités en arrière-plan
Un lancement d'activité en arrière-plan se produit lorsqu'une application qui n'est pas au premier plan, sans activité visible ou un PendingIntent reçu d'une autre application tente de démarrer une nouvelle activité. Il s'agit d'un lancement d'activité en arrière-plan.
Bien qu'il existe des cas d'utilisation légitimes, par exemple lorsqu'une application de réveil démarre, les lancements d'activités en arrière-plan sans restriction entraînent une mauvaise expérience utilisateur et créent des failles de sécurité.
Pourquoi sont-ils limités ?
Depuis Android 10 (niveau d'API 29), la plate-forme a imposé des restrictions sur le moment où les applications peuvent démarrer des activités en arrière-plan. Ces protections permettent d'empêcher les comportements malveillants des applications et d'améliorer l'expérience utilisateur en atténuant les abus courants, y compris les suivants :
- Piratage de l'interface utilisateur et annonces pop-up : une application en arrière-plan lance de manière inattendue une activité (souvent une annonce) au-dessus de l'application avec laquelle l'utilisateur interagit actuellement, piratant ainsi sa session.
- Hameçonnage et usurpation d'identité : une application en arrière-plan lance une activité qui se fait passer pour une autre application (par exemple, un faux écran de connexion pour une application légitime) afin de voler les identifiants de l'utilisateur. Cela se fait souvent par le biais d'une attaque "Activity Sandwich", où une activité malveillante est insérée dans la pile de tâches d'une application légitime.
- Tapjacking : une application en arrière-plan affiche une activité transparente ou masquée au-dessus d'une autre application pour intercepter les appuis de l'utilisateur et l'inciter à effectuer des actions involontaires.
- Réveil de l'application : un composant en arrière-plan d'une application réveille les composants de premier plan d'une autre application pour augmenter de manière illégitime les métriques d'utilisateurs actifs quotidiens.
Services de premier plan (pour les tâches en cours)
Si votre application doit effectuer une tâche de longue durée en arrière-plan dont l'utilisateur doit être informé, comme la lecture de musique ou le suivi d'une séance d'entraînement, vous devez utiliser un service de premier plan. Un service de premier plan doit afficher une notification persistante que l'utilisateur ne peut pas ignorer. Cette notification peut fournir des commandes interactives (par exemple, des boutons de lecture/pause pour une application musicale). L'utilisateur est ainsi informé et garde le contrôle, sans être interrompu par une activité en plein écran.
En suivant cette hiérarchie (en commençant par les notifications standards et en passant à des options plus intrusives uniquement si nécessaire), vous créez une expérience plus prévisible et de meilleure qualité pour vos utilisateurs.
Quand les démarrages d'activités en arrière-plan sont autorisés (exceptions)
Une application peut démarrer une activité en arrière-plan si l'une des conditions suivantes est remplie :
- L'application comporte une fenêtre visible, par exemple une activité au premier plan.
- L'application est l'éditeur de mode de saisie (IME) actuel.
- L'activité est lancée à partir d'un
PendingIntentenvoyé par le système (par exemple, à partir d'un appui sur une notification). - L'utilisateur a accordé à l'application l'autorisation
SYSTEM_ALERT_WINDOW. - L'autorisation
START_ACTIVITIES_FROM_BACKGROUNDa été accordée à l'application. - L'application est liée par un service qui a été autorisé à démarrer des activités en arrière-plan.
- Le lancement est initié par l'application de lancement de l'appareil, par exemple lorsqu'un utilisateur appuie sur l'icône d'une application ou interagit avec un widget.
- Le lancement provient d'une partie essentielle du système d'exploitation qui doit s'exécuter à tout moment, par exemple lorsque le service de téléphonie démarre l'écran d'appel entrant.
Nouvelle protection et activations requises
Pour renforcer la sécurité, Android a introduit des règles plus strictes nécessitant des activations explicites pour les applications qui utilisent PendingIntent et IntentSender pour lancer des activités. Un lancement n'est autorisé que si l'application qui a créé le PendingIntent ou celle qui l'envoie choisit d'accorder ses privilèges de lancement en arrière-plan.
Dans la plupart des cas, l'application qui envoie le PendingIntent doit être celle qui choisit d'activer cette option, car il s'agit généralement de l'application avec laquelle l'utilisateur interagit directement (par exemple, en appuyant sur un bouton).
Les expéditeurs doivent activer PendingIntent
Lorsque votre application cible Android 14 (niveau d'API 34) ou une version ultérieure, elle n'accorde plus ses privilèges de lancement d'activités en arrière-plan par défaut lors de l'envoi d'un PendingIntent. Si vous n'activez pas
explicitement cette option, le lancement de l'activité peut être bloqué, sauf si le créateur
du PendingIntent a déjà accordé ses propres privilèges.
Pour garantir la réussite d'un lancement, l'expéditeur doit activer ses privilèges en appelant ActivityOptions.setPendingIntentBackgroundActivityStartMode() et le mode recommandé est ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE (ajouté dans le SDK 36).
Il s'agit d'un mode plus strict et plus sûr. Il n'accorde l'autorisation que si l'application d'envoi est visible à l'écran au moment où le PendingIntent est envoyé. Cela garantit plus fortement que le lancement de l'activité est le résultat direct de l'interaction d'un utilisateur avec votre application.
Utilisez ActivityOptions.setPendingIntentBackgroundActivityStartMode() pour accorder des droits.
// Sender Side
ActivityOptions options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);
try {
myPendingIntent.send(options.toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "The PendingIntent was canceled", e);
}
// Sender Side
val options = ActivityOptions.makeBasic().apply {
pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE
}
try {
myPendingIntent.send(options.toBundle())
} catch (e: PendingIntent.CanceledException) {
Log.e(TAG, "The PendingIntent was canceled", e)
}
Les créateurs doivent activer PendingIntent
Lorsque votre application cible Android 15 (niveau d'API 35) ou une version ultérieure, une application qui crée un PendingIntent n'accorde plus ses privilèges de lancement en arrière-plan par défaut. Pour autoriser l'expéditeur à utiliser les privilèges de lancement d'activités en arrière-plan de votre application, vous devez activer explicitement cette option.
Utilisez ActivityOptions.setPendingIntentCreatorBackgroundActivityStartMode() pour accorder des privilèges.
// Creator Side
Intent intent = new Intent(context, MyActivity.class);
ActivityOptions options = ActivityOptions.makeBasic().setPendingIntentCreatorBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
PendingIntent pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE, options.toBundle());
// Creator Side
val intent = Intent(context, MyActivity::class.java)
val options = ActivityOptions.makeBasic().apply {
pendingIntentCreatorBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
val pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent,
PendingIntent.FLAG_IMMUTABLE, options.toBundle())
Lancement avec IntentSender
Les mêmes restrictions concernant les lancements d'activités en arrière-plan s'appliquent également lors du lancement d'activités à l'aide d'un
IntentSender. Étant donné qu'un IntentSender est obtenu via
PendingIntent.getIntentSender, il est soumis à des exigences d'activation
similaires.
- À partir d'Android 14 (API 34), l'utilisation de Context.startIntentSender()
nécessite une activation côté expéditeur. Vous devez également fournir le bundle
ActivityOptionsici.
ActivityOptions options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
flagsValues, extraFlags, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
flagsValues, extraFlags, options.toBundle())
- À partir d'Android 17 (API 37+), l'utilisation de IntentSender.sendIntent() nécessite une activation côté expéditeur.
ActivityOptions options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
myIntentSender.sendIntent(context, code, intent, onFinished, handler,
requiredPermission, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
myIntentSender.sendIntent(context, code, intent, onFinished, handler,
requiredPermission, options.toBundle())
- Utilisation d'ActivityResultLauncher
: cette API AndroidX utilise Context.startIntentSender() en interne et est donc affectée par les restrictions concernant les lancements d'activités en arrière-plan.
Schéma de séquence : restrictions concernant les lancements d'activités en arrière-plan
Ce schéma illustre le processus de lancement sécurisé d'une activité à l'aide d'un PendingIntent. La réussite d'un lancement dépend d'une chaîne de privilèges valide dans laquelle au moins l'une des applications participantes accorde ses privilèges et a la possibilité de lancer une activité en arrière-plan.
- Création et délégation (application A – Créateur)
- L'application créatrice crée le
PendingIntent. - Si elle cible le SDK 35 ou une version ultérieure, le créateur doit déléguer explicitement ses privilèges de lancement d'activités en arrière-plan à l'aide de setPendingIntentCreatorBackgroundActivityStartMode() s'il souhaite que ses privilèges soient utilisés. Par défaut, aucun privilège n'est délégué.
- Le
PendingIntentest ensuite transmis à une autre application (application B).
- L'application créatrice crée le
- Lancement et contribution (application B – Expéditeur)
- Ultérieurement, l'application expéditrice lance le lancement en appelant
PendingIntent.send(). - Si elle cible le SDK 34 ou une version ultérieure, l'expéditeur doit explicitement contribuer ses propres privilèges à l'aide de setPendingIntentBackgroundActivityStartMode() s'il souhaite que ses privilèges soient utilisés. Par défaut, aucun privilège n'est délégué.
- Ultérieurement, l'application expéditrice lance le lancement en appelant
- Validation de la sécurité du système Android
- Le système Android intercepte la requête de lancement et effectue une vérification de sécurité.
- Il évalue deux conditions :
- Le créateur a-t-il délégué ses privilèges ? L'application créatrice remplit-elle actuellement l'une des exceptions générales concernant les lancements d'activités en arrière-plan ?
- L'expéditeur a-t-il contribué ses privilèges ? L'application expéditrice remplit-elle actuellement l'une des exceptions générales concernant les lancements d'activités en arrière-plan ?
- Résultat
- AUTORISÉ : si au moins une des deux conditions de l'étape 3 est remplie, la chaîne de privilèges est validée. Le système Android démarre l'activité cible et l'expéditeur reçoit un résultat positif.
- BLOQUÉ : si aucune des deux conditions n'est remplie, le système bloque le lancement. L'application expéditrice ne reçoit pas de valeur de retour directe ni d'exception indiquant l'échec. Au lieu de cela, le système Android enregistre en interne un "Background activity launch blocked!" message dans Logcat, que les développeurs doivent vérifier pour le débogage.
Prévention du piratage de tâches
Pour éviter les attaques de piratage de tâches (comme l'"Activity Sandwich"), Android 15 introduit de nouvelles règles pour les applications ciblant le niveau d'API 37 ou supérieur.
- Règle 1 : dans une même tâche, une activité ne peut être lancée que par une autre activité appartenant à la même application (c'est-à-dire ayant le même UID) que l'activité la plus haute dans la tâche.
- Règle 2 : seule une activité d'une tâche de premier plan qui correspond à l'UID de l'activité la plus haute est autorisée à créer une tâche ou à mettre au premier plan une tâche existante différente.
Activation par le développeur des protections dans les tâches
Ce comportement peut être activé à partir du SDK cible 37. Vous devez l'activer explicitement. Il est conçu pour empêcher le piratage de tâches (ou Activity Sandwiching), où une application malveillante peut lancer une activité dans la tâche de votre application pour se faire passer pour elle et voler les données utilisateur.
Activation des protections
Pour activer l'ASM pour votre application, définissez l'attribut android:allowCrossUidActivitySwitchFromBelow sur "false" dans votre fichier AndroidManifest.xml. Il s'agit d'un paramètre au niveau de l'application qui protège toutes les activités de votre application par défaut.
Création d'exceptions pour des activités spécifiques
Si vous l'avez activé pour votre application, mais que vous devez autoriser d'autres applications à lancer une activité spécifique et approuvée, vous pouvez créer une exception ciblée. Pour exempter une seule activité de cette protection, appelez setAllowCrossUidActivitySwitchFromBelow(true) dans la méthode onCreate() de cette activité. Cela permet de lancer cette activité, tandis que le reste de votre application reste protégé.
Dépannage
Filtrez Logcat pour trouver les messages pertinents à l'aide d'une expression régulière. Le tag ActivityTaskManager est souvent utilisé, et le filtrage par ActivityTaskManager peut aider à isoler les journaux.
Comprendre les messages de journal clés
Lancement bloqué (erreur) : ce message indique qu'un démarrage d'activité a été bloqué.
- Signification : le démarrage d'une activité a été refusé, car l'activation PendingIntent nécessaire était manquante pour l'expéditeur (ciblant le SDK 34 ou une version ultérieure) ou le créateur (ciblant le SDK 35 ou une version ultérieure).
- Action : vous devez mettre à jour votre code pour inclure l'activation ActivityOptions appropriée.
Lorsque vous analysez les journaux, vérifiez les champs suivants :
- realCallingPackage : application qui a envoyé le PendingIntent. Il s'agit de l'expéditeur.
- callingPackage : application qui a créé le PendingIntent. Il s'agit du créateur.
Mode strict
À partir d'Android 16, le développeur d'applications peut activer le mode strict pour être averti lorsqu'un lancement d'activité est bloqué (ou risque d'être bloqué lorsque le SDK cible de l'application est augmenté).
Exemple de code à activer dès le début de votre application, de votre activité ou d'un autre composant d'application dans la méthode Application.onCreate() :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectBlockedBackgroundActivityLaunch()
.penaltyLog()
.build());
)
}