Mise à jour avec Android 8.0 Oreo
Même si la question a été posée à l'origine pour la prise en charge d'Android L, les gens semblent toujours répondre à cette question et à cette réponse, il vaut donc la peine de décrire les améliorations apportées à Android 8.0 Oreo. Les méthodes rétrocompatibles sont toujours décrites ci-dessous.
Qu'est ce qui a changé?
À partir d' Android 8.0 Oreo , le groupe d'autorisations PHONE contient également l' autorisation ANSWER_PHONE_CALLS . Comme le nom de l'autorisation l'indique, le maintenir permet à votre application d'accepter par programme les appels entrants via un appel API approprié sans aucun piratage autour du système en utilisant la réflexion ou en simulant l'utilisateur.
Comment utilisons-nous ce changement?
Vous devez vérifier la version du système au moment de l'exécution si vous prenez en charge les anciennes versions d'Android afin de pouvoir encapsuler ce nouvel appel d'API tout en maintenant la prise en charge de ces anciennes versions d'Android. Vous devez suivre la demande d'autorisations au moment de l'exécution pour obtenir cette nouvelle autorisation pendant l'exécution, comme c'est la norme sur les nouvelles versions d'Android.
Après avoir obtenu l'autorisation, votre application n'a qu'à appeler la méthode acceptRingingCall de TelecomManager . Une invocation de base ressemble alors à ceci:
TelecomManager tm = (TelecomManager) mContext
.getSystemService(Context.TELECOM_SERVICE);
if (tm == null) {
throw new NullPointerException("tm == null");
}
tm.acceptRingingCall();
Méthode 1: TelephonyManager.answerRingingCall ()
Pour lorsque vous avez un contrôle illimité sur l'appareil.
Qu'est-ce que c'est?
Il existe TelephonyManager.answerRingingCall () qui est une méthode interne cachée. Cela fonctionne comme un pont pour ITelephony.answerRingingCall () qui a été discuté sur les interwebs et semble prometteur au début. Il n'est pas disponible sur 4.4.2_r1 car il a été introduit uniquement dans le commit 83da75d pour Android 4.4 KitKat ( ligne 1537 sur 4.4.3_r1 ) et plus tard "réintroduit" dans le commit f1e1e77 pour Lollipop ( ligne 3138 sur 5.0.0_r1 ) en raison de la façon dont le L'arbre Git était structuré. Cela signifie qu'à moins que vous ne preniez en charge uniquement les appareils avec Lollipop, ce qui est probablement une mauvaise décision en raison de la faible part de marché de celui-ci à l'heure actuelle, vous devez toujours fournir des méthodes de secours si vous empruntez cette voie.
Comment utiliserions-nous cela?
Comme la méthode en question est masquée de l'utilisation des applications SDK, vous devez utiliser la réflexion pour examiner et utiliser dynamiquement la méthode pendant l'exécution. Si vous n'êtes pas familier avec la réflexion, vous pouvez lire rapidement Qu'est-ce que la réflexion et pourquoi est-ce utile? . Vous pouvez également approfondir les détails sur Trail: l'API Reflection si cela vous intéresse.
Et à quoi cela ressemble-t-il dans le code?
final String LOG_TAG = "TelephonyAnswer";
TelephonyManager tm = (TelephonyManager) mContext
.getSystemService(Context.TELEPHONY_SERVICE);
try {
if (tm == null) {
throw new NullPointerException("tm == null");
}
tm.getClass().getMethod("answerRingingCall").invoke(tm);
} catch (Exception e) {
Log.e(LOG_TAG, "Unable to use the Telephony Manager directly.", e);
}
C'est trop beau pour être vrai!
En fait, il y a un léger problème. Cette méthode doit être entièrement fonctionnelle, mais le responsable de la sécurité souhaite que les appelants maintiennent android.permission.MODIFY_PHONE_STATE . Cette autorisation est du domaine des fonctionnalités partiellement documentées du système car les tiers ne sont pas censés y toucher (comme vous pouvez le voir dans la documentation correspondante). Vous pouvez essayer d'ajouter un <uses-permission>
pour cela mais cela ne servira à rien car le niveau de protection pour cette autorisation est signature | system ( voir la ligne 1201 de core / AndroidManifest sur 5.0.0_r1 ).
Vous pouvez lire le problème 34785: Mise à jour de la documentation android: protectionLevel qui a été créée en 2012 pour voir qu'il nous manque des détails sur la "syntaxe de canal" spécifique, mais après avoir expérimenté, il semble qu'il doit fonctionner comme un 'ET' signifiant tout le les indicateurs spécifiés doivent être remplis pour que l'autorisation soit accordée. En travaillant sous cette hypothèse, cela signifierait que vous devez avoir votre application:
Installé en tant qu'application système.
Cela devrait être bien et pourrait être accompli en demandant aux utilisateurs d'installer à l'aide d'un ZIP lors de la récupération, par exemple lors de l'enracinement ou de l'installation d'applications Google sur des ROM personnalisées qui ne les ont pas déjà emballées.
Signé avec la même signature que les frameworks / base aka le système, aka la ROM.
C'est là que les problèmes surgissent. Pour ce faire, vous devez avoir la main sur les clés utilisées pour la signature des frameworks / base. Vous devrez non seulement accéder aux clés de Google pour les images d'usine Nexus, mais également accéder aux clés de tous les autres OEM et développeurs de ROM. Cela ne semble pas plausible, vous pouvez donc faire signer votre application avec les clés système en créant une ROM personnalisée et en demandant à vos utilisateurs d'y basculer (ce qui peut être difficile) ou en trouvant un exploit avec lequel le niveau de protection des autorisations peut être contourné (ce qui peut aussi être difficile).
En outre, ce comportement semble être lié au problème 34792: Android Jelly Bean / 4.1: android.permission.READ_LOGS ne fonctionne plus qui utilise le même niveau de protection avec un indicateur de développement non documenté.
Travailler avec TelephonyManager semble bon, mais ne fonctionnera que si vous obtenez l'autorisation appropriée, ce qui n'est pas si facile à faire en pratique.
Qu'en est-il de l'utilisation de TelephonyManager d'une autre manière?
Malheureusement, il semble que vous deviez détenir le fichier android.permission.MODIFY_PHONE_STATE pour utiliser les outils sympas, ce qui signifie que vous aurez du mal à accéder à ces méthodes.
Méthode 2: appel de service CODE DE SERVICE
Pour quand vous pouvez tester que la construction exécutée sur l'appareil fonctionnera avec le code spécifié.
Sans pouvoir interagir avec le TelephonyManager, il y a aussi la possibilité d'interagir avec le service via l' service
exécutable.
Comment cela marche-t-il?
C'est assez simple, mais il y a encore moins de documentation sur cette route que d'autres. Nous savons avec certitude que l'exécutable prend deux arguments - le nom du service et le code.
Le nom du service que nous voulons utiliser est téléphone .
Cela peut être vu en exécutant service list
.
Le code que nous voulons utiliser semble avoir été 6 mais semble maintenant être 5 .
Il semble qu'il ait été basé sur IBinder.FIRST_CALL_TRANSACTION + 5 pour de nombreuses versions maintenant (de 1.5_r4 à 4.4.4_r1 ) mais lors des tests locaux, le code 5 a fonctionné pour répondre à un appel entrant. Comme Lollipo est une mise à jour massive tout autour, il est compréhensible que les internes aient également changé ici.
Cela se traduit par une commande de service call phone 5
.
Comment utilisons-nous cela par programme?
Java
Le code suivant est une implémentation approximative conçue pour fonctionner comme une preuve de concept. Si vous voulez vraiment continuer et utiliser cette méthode, vous voudrez probablement consulter les directives pour une utilisation sans problème de su et éventuellement passer au libsuperuser plus développé de Chainfire .
try {
Process proc = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(proc.getOutputStream());
os.writeBytes("service call phone 5\n");
os.flush();
os.writeBytes("exit\n");
os.flush();
if (proc.waitFor() == 255) {
}
} catch (IOException e) {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Manifeste
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>
Cela nécessite-t-il vraiment un accès root?
Malheureusement, cela semble être le cas. Vous pouvez essayer d'utiliser Runtime.exec dessus, mais je n'ai pas pu avoir de chance avec cette route.
À quel point est-ce stable?
Je suis content que vous ayez posé la question. En raison de l'absence de documentation, cela peut se rompre entre différentes versions, comme illustré par la différence de code apparente ci-dessus. Le nom du service devrait probablement rester téléphone dans diverses versions, mais pour autant que nous sachions, la valeur du code peut changer entre plusieurs versions de la même version (modifications internes par, par exemple, le skin de l'OEM), à son tour cassant la méthode utilisée. Il convient donc de mentionner que les tests ont eu lieu sur un Nexus 4 (mako / occam). Je vous déconseille personnellement d'utiliser cette méthode, mais comme je ne suis pas en mesure de trouver une méthode plus stable, je pense que c'est la meilleure solution.
Méthode originale: intentions de code-clé du casque
Pour les moments où vous devez vous installer.
La section suivante a été fortement influencée par cette réponse par Riley C .
La méthode d'intention de casque simulée telle que publiée dans la question d'origine semble être diffusée exactement comme on pouvait s'y attendre, mais elle ne semble pas atteindre l'objectif de répondre à l'appel. Bien qu'il semble y avoir du code en place qui devrait gérer ces intentions, ils ne sont tout simplement pas pris en compte, ce qui doit signifier qu'il doit y avoir une sorte de nouvelles contre-mesures en place contre cette méthode. Le journal ne montre rien d'intéressant non plus et je ne pense pas personnellement que creuser dans la source Android pour cela vaudra la peine juste en raison de la possibilité que Google introduise un léger changement qui rompt facilement la méthode utilisée de toute façon.
Y a-t-il quelque chose que nous pouvons faire maintenant?
Le comportement peut être reproduit de manière cohérente à l'aide de l'exécutable d'entrée. Il prend un argument keycode, pour lequel nous passons simplement KeyEvent.KEYCODE_HEADSETHOOK . La méthode ne nécessite même pas d'accès root, ce qui la rend adaptée aux cas d'utilisation courants dans le grand public, mais il y a un petit inconvénient dans la méthode - l'événement de pression sur le bouton du casque ne peut pas être spécifié pour exiger une autorisation, ce qui signifie qu'il fonctionne comme un vrai appuyer sur un bouton et faire des bulles tout au long de la chaîne, ce qui signifie que vous devez être prudent quant au moment de simuler la pression sur le bouton car cela pourrait, par exemple, déclencher le lecteur de musique pour démarrer la lecture si personne d'autre de priorité plus élevée n'est prêt à gérer l'événement.
Code?
new Thread(new Runnable() {
@Override
public void run() {
try {
Runtime.getRuntime().exec("input keyevent " +
Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK));
} catch (IOException e) {
String enforcedPerm = "android.permission.CALL_PRIVILEGED";
Intent btnDown = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra(
Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_HEADSETHOOK));
Intent btnUp = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra(
Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_HEADSETHOOK));
mContext.sendOrderedBroadcast(btnDown, enforcedPerm);
mContext.sendOrderedBroadcast(btnUp, enforcedPerm);
}
}
}).start();
tl; dr
Il existe une belle API publique pour Android 8.0 Oreo et versions ultérieures.
Il n'y a pas d'API publique avant Android 8.0 Oreo. Les API internes sont interdites ou simplement sans documentation. Vous devez procéder avec prudence.