Update mit Android 8.0 Oreo
Obwohl die Frage ursprünglich für die Unterstützung von Android L gestellt wurde, scheinen die Leute diese Frage und Antwort immer noch zu beantworten. Es lohnt sich daher, die in Android 8.0 Oreo eingeführten Verbesserungen zu beschreiben. Die abwärtskompatiblen Methoden werden nachstehend noch beschrieben.
Was hat sich geändert?
Beginnend mit Android 8.0 Oreo , die PHONE Berechtigungsgruppe enthält auch die ANSWER_PHONE_CALLS Erlaubnis . Wie der Name der Berechtigung andeutet, kann Ihre App eingehende Anrufe programmgesteuert über einen geeigneten API-Aufruf annehmen, ohne das System mithilfe von Reflektion oder Simulation des Benutzers zu hacken.
Wie nutzen wir diese Änderung?
Sie sollten die Systemversion zur Laufzeit überprüfen, wenn Sie ältere Android-Versionen unterstützen, damit Sie diesen neuen API-Aufruf kapseln und gleichzeitig die Unterstützung für diese älteren Android-Versionen beibehalten können. Sie sollten zur Laufzeit Berechtigungen anfordern , um diese neue Berechtigung zur Laufzeit zu erhalten, wie dies bei neueren Android-Versionen Standard ist.
Nachdem Sie die Berechtigung erhalten haben, muss Ihre App lediglich die acceptRingingCall- Methode des TelecomManager aufrufen . Ein grundlegender Aufruf sieht dann wie folgt aus:
TelecomManager tm = (TelecomManager) mContext
.getSystemService(Context.TELECOM_SERVICE);
if (tm == null) {
// whether you want to handle this is up to you really
throw new NullPointerException("tm == null");
}
tm.acceptRingingCall();
Methode 1: TelephonyManager.answerRingingCall ()
Für den Fall, dass Sie uneingeschränkte Kontrolle über das Gerät haben.
Was ist das?
Es gibt TelephonyManager.answerRingingCall (), eine versteckte interne Methode. Es fungiert als Brücke für ITelephony.answerRingingCall (), das in den Interwebs diskutiert wurde und zu Beginn vielversprechend erscheint. Es ist unter 4.4.2_r1 nicht verfügbar, da es nur in Commit 83da75d für Android 4.4 KitKat ( Zeile 1537 in 4.4.3_r1 ) eingeführt und später in Commit f1e1e77 für Lollipop ( Zeile 3138 in 5.0.0_r1 ) "wieder eingeführt" wurde Git-Baum wurde strukturiert. Dies bedeutet, dass Sie, wenn Sie nicht nur Geräte mit Lollipop unterstützen, was aufgrund des geringen Marktanteils derzeit wahrscheinlich eine schlechte Entscheidung ist, immer noch Fallback-Methoden bereitstellen müssen, wenn Sie diesen Weg gehen.
Wie würden wir das nutzen?
Da die betreffende Methode vor der Verwendung durch SDK-Anwendungen verborgen ist, müssen Sie Reflection verwenden , um die Methode zur Laufzeit dynamisch zu untersuchen und zu verwenden. Wenn Sie mit Reflexion nicht vertraut sind, können Sie schnell lesen, was Reflexion ist und warum sie nützlich ist. . Sie können sich auch eingehender mit den Besonderheiten von Trail: The Reflection API befassen, wenn Sie daran interessiert sind.
Und wie sieht das im Code aus?
// set the logging tag constant; you probably want to change this
final String LOG_TAG = "TelephonyAnswer";
TelephonyManager tm = (TelephonyManager) mContext
.getSystemService(Context.TELEPHONY_SERVICE);
try {
if (tm == null) {
// this will be easier for debugging later on
throw new NullPointerException("tm == null");
}
// do reflection magic
tm.getClass().getMethod("answerRingingCall").invoke(tm);
} catch (Exception e) {
// we catch it all as the following things could happen:
// NoSuchMethodException, if the answerRingingCall() is missing
// SecurityException, if the security manager is not happy
// IllegalAccessException, if the method is not accessible
// IllegalArgumentException, if the method expected other arguments
// InvocationTargetException, if the method threw itself
// NullPointerException, if something was a null value along the way
// ExceptionInInitializerError, if initialization failed
// something more crazy, if anything else breaks
// TODO decide how to handle this state
// you probably want to set some failure state/go to fallback
Log.e(LOG_TAG, "Unable to use the Telephony Manager directly.", e);
}
Das ist zu gut um wahr zu sein!
Eigentlich gibt es ein kleines Problem. Diese Methode sollte voll funktionsfähig sein, aber der Sicherheitsmanager möchte, dass Anrufer android.permission.MODIFY_PHONE_STATE halten . Diese Berechtigung gilt nur für teilweise dokumentierte Funktionen des Systems, da von Dritten nicht erwartet wird, dass sie diese berühren (wie Sie der Dokumentation entnehmen können). Sie können versuchen, ein <uses-permission>
dafür hinzuzufügen , aber das nützt nichts, da die Schutzstufe für diese Berechtigung signatur | system ist ( siehe Zeile 1201 von core / AndroidManifest unter 5.0.0_r1 ).
Sie können die Ausgabe 34785: Update android: ProtectionLevel-Dokumentation lesen, die bereits 2012 erstellt wurde, um festzustellen , dass uns Details zur spezifischen "Pipe-Syntax" fehlen. Beim Experimentieren scheint sie jedoch als "UND" zu funktionieren, was bedeutet, dass alle Die angegebenen Flags müssen erfüllt sein, damit die Erlaubnis erteilt werden kann. Wenn Sie unter dieser Annahme arbeiten, bedeutet dies, dass Sie Ihre Bewerbung haben müssen:
Als Systemanwendung installiert.
Dies sollte in Ordnung sein und kann erreicht werden, indem die Benutzer aufgefordert werden, bei der Wiederherstellung eine ZIP-Datei zu installieren, z. B. beim Rooten oder Installieren von Google Apps auf benutzerdefinierten ROMs, auf denen diese noch nicht gepackt sind.
Signiert mit der gleichen Signatur wie Frameworks / Base, auch bekannt als System, auch bekannt als ROM.
Hier tauchen die Probleme auf. Dazu müssen Sie die Schlüssel zum Signieren von Frameworks / Base in der Hand haben. Sie müssten nicht nur auf die Schlüssel von Google für Nexus-Factory-Images zugreifen, sondern auch auf alle Schlüssel anderer OEMs und ROM-Entwickler. Dies erscheint nicht plausibel, sodass Sie Ihre Anwendung mit den Systemschlüsseln signieren lassen können, indem Sie entweder ein benutzerdefiniertes ROM erstellen und Ihre Benutzer auffordern, zu diesem zu wechseln (was möglicherweise schwierig ist) oder indem Sie einen Exploit finden, mit dem die Berechtigungsschutzstufe umgangen werden kann (was auch schwierig sein könnte).
Darüber hinaus scheint dieses Verhalten mit Problem 34792: Android Jelly Bean / 4.1: android.permission.READ_LOGS in Zusammenhang zu stehen, das nicht mehr funktioniert und dieselbe Schutzstufe zusammen mit einem undokumentierten Entwicklungsflag verwendet.
Die Arbeit mit dem TelephonyManager klingt gut, funktioniert aber nur, wenn Sie die entsprechende Berechtigung erhalten, was in der Praxis nicht so einfach ist.
Was ist mit der Verwendung von TelephonyManager auf andere Weise?
Leider müssen Sie anscheinend die android.permission.MODIFY_PHONE_STATE gedrückt halten , um die coolen Tools verwenden zu können, was wiederum bedeutet, dass Sie Schwierigkeiten haben werden, auf diese Methoden zuzugreifen.
Methode 2: Serviceabruf SERVICE CODE
Wenn Sie testen können, ob der auf dem Gerät ausgeführte Build mit dem angegebenen Code funktioniert.
Ohne mit dem TelephonyManager interagieren zu können, besteht auch die Möglichkeit, über die service
ausführbare Datei mit dem Dienst zu interagieren .
Wie funktioniert das?
Es ist ziemlich einfach, aber es gibt noch weniger Dokumentation über diese Route als über andere. Wir wissen mit Sicherheit, dass die ausführbare Datei zwei Argumente enthält - den Dienstnamen und den Code.
Der Dienstname, den wir verwenden möchten, ist Telefon .
Dies kann durch Laufen gesehen werden service list
.
Der Code, den wir verwenden möchten, scheint 6 gewesen zu sein, scheint aber jetzt 5 zu sein .
Es sieht so aus, als ob es für viele Versionen (von 1.5_r4 bis 4.4.4_r1 ) auf IBinder.FIRST_CALL_TRANSACTION + 5 basiert , aber während lokaler Tests hat der Code 5 einen eingehenden Anruf beantwortet. Da es sich bei Lollipo um ein umfangreiches Update handelt, sind verständlicherweise auch hier die Interna geändert worden.
Dies ergibt sich mit einem Befehl von service call phone 5
.
Wie nutzen wir das programmatisch?
Java
Der folgende Code ist eine grobe Implementierung, die als Proof of Concept dient. Wenn Sie diese Methode tatsächlich verwenden möchten, sollten Sie sich wahrscheinlich die Richtlinien für eine problemlose Verwendung von su ansehen und möglicherweise zum vollständig entwickelten libsuperuser von Chainfire wechseln .
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) {
// TODO handle being declined root access
// 255 is the standard code for being declined root for SU
}
} catch (IOException e) {
// TODO handle I/O going wrong
// this probably means that the device isn't rooted
} catch (InterruptedException e) {
// don't swallow interruptions
Thread.currentThread().interrupt();
}
Manifest
<!-- Inform the user we want them root accesses. -->
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>
Benötigt dies wirklich Root-Zugriff?
Leider scheint es so. Sie können versuchen, Runtime.exec darauf zu verwenden, aber ich konnte mit dieser Route kein Glück haben.
Wie stabil ist das?
Ich bin froh, dass du gefragt hast. Da dies nicht dokumentiert ist, kann es zu verschiedenen Versionen kommen, wie der scheinbare Codeunterschied oben zeigt. Der Dienstname sollte wahrscheinlich über verschiedene Builds hinweg telefonisch bleiben , aber nach allem, was wir wissen, kann sich der Codewert über mehrere Builds derselben Version hinweg ändern (interne Änderungen, z. B. durch den OEM-Skin), was wiederum die verwendete Methode verletzt. Erwähnenswert ist daher, dass die Tests an einem Nexus 4 (mako / occam) durchgeführt wurden. Ich würde Ihnen persönlich davon abraten, diese Methode anzuwenden, aber da ich keine stabilere Methode finden kann, glaube ich, dass dies der beste Schuss ist.
Ursprüngliche Methode: Absichten des Headset-Schlüsselcodes
Für Zeiten, in denen Sie sich niederlassen müssen.
Der folgende Abschnitt wurde stark von dieser Antwort von Riley C beeinflusst .
Die in der ursprünglichen Frage angegebene simulierte Headset-Intent-Methode scheint wie erwartet gesendet zu werden, scheint jedoch das Ziel der Beantwortung des Anrufs nicht zu erreichen. Es scheint zwar Code zu geben, der diese Absichten handhaben sollte, aber sie werden einfach nicht beachtet, was bedeuten muss, dass gegen diese Methode neue Gegenmaßnahmen ergriffen werden müssen. Das Protokoll zeigt auch nichts Interessantes und ich persönlich glaube nicht, dass es sich lohnen wird, die Android-Quelle zu durchsuchen, nur weil Google möglicherweise eine geringfügige Änderung einführt, die die ohnehin verwendete Methode leicht bricht.
Können wir jetzt etwas tun?
Das Verhalten kann mithilfe der ausführbaren Eingabedatei konsistent reproduziert werden. Es nimmt ein Schlüsselcode-Argument auf, für das wir einfach KeyEvent.KEYCODE_HEADSETHOOK übergeben . Die Methode erfordert nicht einmal Root-Zugriff, sodass sie für allgemeine Anwendungsfälle in der Öffentlichkeit geeignet ist. Die Methode weist jedoch einen kleinen Nachteil auf: Das Ereignis zum Drücken der Headset-Taste kann nicht so angegeben werden, dass eine Berechtigung erforderlich ist, was bedeutet, dass es wie ein echtes Ereignis funktioniert Tastendruck und sprudelt durch die gesamte Kette, was wiederum bedeutet, dass Sie vorsichtig sein müssen, wann der Tastendruck simuliert werden soll, da dies beispielsweise den Musikplayer dazu veranlassen könnte, die Wiedergabe zu starten, wenn niemand anderes mit höherer Priorität bereit ist, damit umzugehen das Ereignis.
Code?
new Thread(new Runnable() {
@Override
public void run() {
try {
Runtime.getRuntime().exec("input keyevent " +
Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK));
} catch (IOException e) {
// Runtime.exec(String) had an I/O problem, try to fall back
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
Es gibt eine schöne öffentliche API für Android 8.0 Oreo und höher.
Vor Android 8.0 Oreo gibt es keine öffentliche API. Die internen APIs sind tabu oder einfach ohne Dokumentation. Sie sollten mit Vorsicht vorgehen.