Aggiorna con Android 8.0 Oreo
Anche se la domanda è stata inizialmente posta per il supporto di Android L, le persone sembrano ancora rispondere a questa domanda e risposta, quindi vale la pena descrivere i miglioramenti introdotti in Android 8.0 Oreo. I metodi compatibili con le versioni precedenti sono ancora descritti di seguito.
Cosa è cambiato?
A partire da Android 8.0 Oreo , il gruppo di autorizzazioni PHONE contiene anche l' autorizzazione ANSWER_PHONE_CALLS . Come suggerisce il nome dell'autorizzazione, tenerlo premuto consente alla tua app di accettare a livello di codice le chiamate in arrivo tramite una chiamata API appropriata senza alcun hacking nel sistema utilizzando la riflessione o la simulazione dell'utente.
Come utilizziamo questo cambiamento?
È necessario controllare la versione del sistema in fase di esecuzione se si supportano versioni precedenti di Android in modo da poter incapsulare questa nuova chiamata API mantenendo il supporto per quelle versioni precedenti di Android. È necessario seguire la richiesta di autorizzazioni in fase di esecuzione per ottenere quella nuova autorizzazione durante l'esecuzione, come è standard nelle versioni Android più recenti.
Dopo aver ottenuto l'autorizzazione, la tua app deve semplicemente chiamare il metodo acceptRingingCall di TelecomManager . Un'invocazione di base appare quindi come segue:
TelecomManager tm = (TelecomManager) mContext
.getSystemService(Context.TELECOM_SERVICE);
if (tm == null) {
throw new NullPointerException("tm == null");
}
tm.acceptRingingCall();
Metodo 1: TelephonyManager.answerRingingCall ()
Per quando hai un controllo illimitato sul dispositivo.
Cos'è questo?
C'è TelephonyManager.answerRingingCall () che è un metodo interno nascosto. Funziona come un ponte per ITelephony.answerRingingCall () che è stato discusso su interweb e sembra promettente all'inizio. E ' non è accessibile 4.4.2_r1 come è stato introdotto solo nel commettere 83da75d per Android 4.4 KitKat ( linea 1537 su 4.4.3_r1 ) e successivamente "reintrodotto" a commettere f1e1e77 per Lollipop ( linea 3138 su 5.0.0_r1 ) a causa di come il L'albero di Git è stato strutturato. Ciò significa che a meno che tu non supporti solo i dispositivi con Lollipop, che probabilmente è una cattiva decisione in base alla piccola quota di mercato di questo momento, devi comunque fornire metodi di riserva se segui questa strada.
Come lo useremmo?
Poiché il metodo in questione è nascosto dall'uso delle applicazioni SDK, è necessario utilizzare la reflection per esaminare e utilizzare dinamicamente il metodo durante il runtime. Se non hai familiarità con la riflessione, puoi leggere rapidamente Cos'è la riflessione e perché è utile? . Puoi anche approfondire le specifiche su Trail: The Reflection API se sei interessato a farlo.
E come appare nel codice?
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);
}
È troppo bello per essere vero!
In realtà, c'è un piccolo problema. Questo metodo dovrebbe essere completamente funzionante, ma il responsabile della sicurezza vuole che i chiamanti trattengano android.permission.MODIFY_PHONE_STATE . Questa autorizzazione è nel regno delle funzionalità del sistema solo parzialmente documentate in quanto non ci si aspetta che terze parti lo tocchino (come puoi vedere dalla relativa documentazione). Puoi provare ad aggiungerne uno <uses-permission>
, ma non servirà a nulla perché il livello di protezione per questa autorizzazione è signature | system ( vedi riga 1201 di core / AndroidManifest su 5.0.0_r1 ).
Puoi leggere il problema 34785: Aggiorna la documentazione di android: protectionLevel che è stata creata nel 2012 per vedere che mancano i dettagli sulla specifica "sintassi pipe", ma dalla sperimentazione sembra che debba funzionare come un "AND" che significa tutti i i flag specificati devono essere soddisfatti affinché l'autorizzazione venga concessa. Partendo da questo presupposto, significherebbe che devi avere la tua applicazione:
Installato come applicazione di sistema.
Questo dovrebbe andare bene e potrebbe essere ottenuto chiedendo agli utenti di installare utilizzando uno ZIP in ripristino, ad esempio durante il rooting o l'installazione di app Google su ROM personalizzate che non le hanno già impacchettate.
Firmato con la stessa firma di framework / base, ovvero il sistema, ovvero la ROM.
È qui che sorgono i problemi. Per fare ciò, è necessario avere le mani sulle chiavi utilizzate per firmare framework / base. Non solo dovresti avere accesso alle chiavi di Google per le immagini di fabbrica Nexus, ma dovresti anche avere accesso a tutte le chiavi di tutti gli altri OEM e sviluppatori ROM. Questo non sembra plausibile, quindi puoi far firmare la tua applicazione con le chiavi di sistema creando una ROM personalizzata e chiedendo ai tuoi utenti di passare ad essa (che potrebbe essere difficile) o trovando un exploit con cui il livello di protezione dei permessi può essere aggirato (che potrebbe anche essere difficile).
Inoltre, questo comportamento sembra essere correlato al problema 34792: Android Jelly Bean / 4.1: android.permission.READ_LOGS non funziona più che utilizza lo stesso livello di protezione insieme a un flag di sviluppo non documentato.
Lavorare con TelephonyManager suona bene, ma non funzionerà a meno che non si ottenga l'autorizzazione appropriata che non è così facile da fare in pratica.
Che dire dell'utilizzo di TelephonyManager in altri modi?
Purtroppo, sembra che ti richieda di tenere android.permission.MODIFY_PHONE_STATE per utilizzare gli strumenti interessanti, il che a sua volta significa che avrai difficoltà ad accedere a quei metodi.
Metodo 2: chiamata di servizio CODICE DI SERVIZIO
Quando puoi verificare che la build in esecuzione sul dispositivo funzioni con il codice specificato.
Senza poter interagire con il TelephonyManager, c'è anche la possibilità di interagire con il servizio tramite l' service
eseguibile.
Come funziona?
È abbastanza semplice, ma c'è ancora meno documentazione su questo percorso rispetto ad altri. Sappiamo per certo che l'eseguibile accetta due argomenti: il nome del servizio e il codice.
Il nome del servizio che vogliamo utilizzare è telefono .
Questo può essere visto correndo service list
.
Il codice che vogliamo utilizzare sembra essere 6 ma ora sembra essere 5 .
Sembra che sia stato basato su IBinder.FIRST_CALL_TRANSACTION + 5 per molte versioni ora (da 1.5_r4 a 4.4.4_r1 ) ma durante il test locale il codice 5 ha funzionato per rispondere a una chiamata in arrivo. Poiché Lollipo è un enorme aggiornamento in tutto e per tutto, è comprensibile che anche gli interni siano cambiati qui.
Ciò risulta con un comando di service call phone 5
.
Come lo utilizziamo a livello di programmazione?
Giava
Il codice seguente è un'implementazione approssimativa realizzata per funzionare come una prova di concetto. Se si vuole realmente andare avanti e utilizzare questo metodo, probabilmente avrete bisogno di controllare le linee guida per senza problemi su utilizzo e, eventualmente, passare al più pienamente sviluppato libsuperuser da 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();
}
Manifesto
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>
Questo richiede davvero l'accesso come root?
Purtroppo, sembra così. Puoi provare a utilizzare Runtime.exec su di esso, ma non sono stato in grado di avere fortuna con quel percorso.
Quanto è stabile questo?
Sono contento che tu l'abbia chiesto. Dato che non è documentato, questo può rompersi in varie versioni, come illustrato dall'apparente differenza di codice sopra. Il nome del servizio dovrebbe probabilmente rimanere sul telefono in varie build, ma per quanto ne sappiamo, il valore del codice può cambiare tra più build della stessa versione (modifiche interne, ad esempio, la skin dell'OEM) a sua volta interrompendo il metodo utilizzato. Vale quindi la pena ricordare che il test è avvenuto su un Nexus 4 (mako / occam). Personalmente ti sconsiglio di utilizzare questo metodo, ma poiché non sono in grado di trovare un metodo più stabile, credo che questo sia lo scatto migliore.
Metodo originale: intenti del codice chiave dell'auricolare
Per i momenti in cui devi accontentarti.
La sezione seguente è stata fortemente influenzata da questa risposta da Riley C .
Il metodo di intento delle cuffie simulato come pubblicato nella domanda originale sembra essere trasmesso proprio come ci si aspetterebbe, ma non sembra raggiungere l'obiettivo di rispondere alla chiamata. Anche se sembra che ci sia del codice che dovrebbe gestire questi intenti, semplicemente non ci si prende cura di loro, il che deve significare che ci devono essere una sorta di nuove contromisure in atto contro questo metodo. Il registro non mostra nulla di interessante e personalmente non credo che scavare attraverso la fonte di Android per questo sarà utile solo per la possibilità che Google introduca una leggera modifica che interrompe facilmente il metodo utilizzato comunque.
C'è qualcosa che possiamo fare adesso?
Il comportamento può essere riprodotto in modo coerente utilizzando l'eseguibile di input. Accetta un argomento del codice chiave, per il quale passiamo semplicemente KeyEvent.KEYCODE_HEADSETHOOK . Il metodo non richiede nemmeno l'accesso root rendendolo adatto a casi d'uso comuni nel pubblico in generale, ma c'è un piccolo inconveniente nel metodo: non è possibile specificare l'evento di pressione del pulsante dell'auricolare per richiedere un'autorizzazione, il che significa che funziona come un vero premere il pulsante e far scorrere l'intera catena, il che a sua volta significa che devi essere cauto su quando simulare la pressione del pulsante in quanto potrebbe, ad esempio, attivare il lettore musicale per avviare la riproduzione se nessun altro con priorità più alta è pronto a gestirlo l'evento.
Codice?
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
C'è una bella API pubblica per Android 8.0 Oreo e versioni successive.
Non esiste un'API pubblica prima di Android 8.0 Oreo. Le API interne sono off-limits o semplicemente senza documentazione. Dovresti procedere con cautela.