Intrdouction
Cet article présente la conception d'une application Android permettant d'automatiser les clics lors de l'installation d'APK sur les appareils sous MIUI. Le système MIUI affiche fréquemment des boîtes de dialogue demandant une confirmation manuelle, ce qui complique les processus automatisés comme les tests logiciels. L'objectif ici est de contourner cette limitation en utilisant le AccessibilityService, sans nécessiter de droits root.
L'application développée fonctionne principalement en deux étapes : vérification de l'activation du service d'accessibilité, puis simulation automatique des interactions utilisateur pendant l’installation.
Prérequis techniques
- Appareil testé : Xiaomi Mi 10
- Outil de développement : Android Studio (version 11.0.10)
- Envirnonement : Windows 10
- API cible : Android 7.0+ (Nougat), nécessaire pour utiliser
GestureDescription
Architecture du projet
Le cœur du fonctionnement repose sur deux composants clés :
- Vérification et activation du service d’accessibilité.
- Utilisation de
AccessibilityServicepour détecter et interagir avec les fenêtres d’installation.
Implémentation du service d’accessibilité
Pour capter les événements système liés à l’installation, nous créons une classe héritant de AccessibilityService.
public class AutoInstallService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
int eventType = event.getEventType();
CharSequence packageName = event.getPackageName();
if (packageName == null) return;
// Filtrer les événements pertinents
if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED ||
eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
if (isInstallerPackage(packageName.toString())) {
handleInstallationPrompt(event);
}
}
}
private boolean isInstallerPackage(String pkg) {
return "com.miui.packageinstaller".equals(pkg) ||
"com.android.packageinstaller".equals(pkg);
}
@Override
public void onInterrupt() {
Log.i("AutoInstall", "Service interrompu");
}
}
Analyse des événements système
event.getEventType(): retourne le type d’événement (ex: changement d’état de fenêtre).event.getPackageName(): identifie l’application à l’origine de l’événement.getRootInActiveWindow()ouevent.getSource(): fournit l’arborescence des éléments UI actifs.
Les types d’événements critiques sont :
TYPE_WINDOW_STATE_CHANGED: déclenché lors de l’ouverture d’une nouvelle activité (ex: écran d’installasion).TYPE_WINDOW_CONTENT_CHANGED: utilisé si le contenu se met à jour dynamiquement.
Gestion des boutons d’autorisation
Une fois l’écran d’installation détecté, le service recherche des boutons contenant des libellés tels que « Autoriser », « Continuer », « Installer ».
private void handleInstallationPrompt(AccessibilityEvent event) {
AccessibilityNodeInfo source = event.getSource();
if (source == null) return;
String[] keywords = {"Autoriser", "Permettre", "Continuer", "Suivant", "Installer", "Terminer"};
for (String keyword : keywords) {
List<AccessibilityNodeInfo> nodes = source.findAccessibilityNodeInfosByText(keyword);
if (nodes != null && !nodes.isEmpty()) {
for (AccessibilityNodeInfo node : nodes) {
if (attemptClick(node)) {
return;
}
}
}
}
}
Stratégies de clic
Deux méthodes sont combinées pour maximiser les chances de succès :
1. Clic standard via action sur le nœud
La méthode recommandée consiste à utiliser performAction sur un nœud interactif.
@TargetApi(Build.VERSION_CODES.N)
private boolean attemptClick(AccessibilityNodeInfo node) {
if (node.isClickable() && node.isEnabled()) {
return node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
// Tentative alternative pour les boutons non cliquables mais visibles
if ("android.widget.Button".equals(node.getClassName()) ||
"android.widget.TextView".equals(node.getClassName())) {
return simulateTap(node);
}
return false;
}
2. Simulation tactile via GestureDescription
Sur certains appareils MIUI, les boutons ne répondent pas aux actions standards. On simule alors un tap physique.
@TargetApi(Build.VERSION_CODES.N)
private boolean simulateTap(AccessibilityNodeInfo node) {
Rect bounds = new Rect();
node.getBoundsInScreen(bounds);
int x = (bounds.left + bounds.right) / 2;
int y = (bounds.top + bounds.bottom) / 2;
Path path = new Path();
path.moveTo(x, y);
GestureDescription.StrokeDescription stroke =
new GestureDescription.StrokeDescription(path, 50, 100);
GestureDescription.Builder builder = new GestureDescription.Builder();
builder.addStroke(stroke);
return dispatchGesture(builder.build(), new GestureResultCallback() {
@Override
public void onCompleted(GestureDescription gestureDescription) {
super.onCompleted(gestureDescription);
}
@Override
public void onCancelled(GestureDescription gestureDescription) {
super.onCancelled(gestureDescription);
}
}, null);
}
Notes sur GestureDescription
- Nécessite Android 7.0+ (
Build.VERSION_CODES.N). - Permet de simuler des gestes sans accès root.
- Doit être utilisé avec précaution pour éviter les faux positifs.
Activation du service d’accessibilité
Pour guider l’utilisateur vers les paramètres, on lance une intention explicite :
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
Il est conseillé d’ajouter une vérification au démarrage de l’application pour s’assurer que le service est activé.
Conclusion partielle
Cette solution permet d’automatiser efficacement les installations sur MIUI, même lorsque les boutons ne sont pas accessibles via les méthodes traditionnelles. Elle repose entièrement sur les capacités fournies par le framework Android, sans besoin de modification système.