In che modo è possibile rispondere alle chiamate in arrivo in modo programmatico in Android 5.0 (Lollipop)?


89

Mentre cerco di creare una schermata personalizzata per le chiamate in arrivo, cerco di rispondere programmaticamente a una chiamata in arrivo. Sto usando il seguente codice ma non funziona in Android 5.0.

// Simulate a press of the headset button to pick up the call
Intent buttonDown = new Intent(Intent.ACTION_MEDIA_BUTTON);             
buttonDown.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
context.sendOrderedBroadcast(buttonDown, "android.permission.CALL_PRIVILEGED");

// froyo and beyond trigger on buttonUp instead of buttonDown
Intent buttonUp = new Intent(Intent.ACTION_MEDIA_BUTTON);               
buttonUp.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));
context.sendOrderedBroadcast(buttonUp, "android.permission.CALL_PRIVILEGED");

oh uomo, perché incappare in questo, fai scorrere l'uomo! mi sembra più facile \ m /
nobalG

Sto creando una schermata di chiamata in arrivo personalizzata per gli utenti Android.
maveroid

2
Qualcuno? Mi interessa anche questo! Ho provato molte cose, ma non hanno funzionato: /
Arthur

1
@nobalG sta dicendo in modo programmatico
dsharew

1
@maveroid, hai trovato una soluzione alternativa per Android 5.0?
arthursfreire

Risposte:


156

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) {
    // whether you want to handle this is up to you really
    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?

// 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);
}

È 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:

  1. 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.

  2. 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' serviceeseguibile.

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) {
        // 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();
}

Manifesto

<!-- Inform the user we want them root accesses. -->
<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) {
            // 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

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.


Per quanto riguarda gli intenti del codice chiave dell'auricolare, hai controllato la fonte di Google qui per qualche motivo per cui smetterebbe di essere perseguito? La cosa divertente è che le chiamate possono ancora essere facilmente rifiutate con questi intenti (basta emulare una pressione prolungata), ma niente funziona per rispondere. Devo ancora trovare un controllo esplicito dell'autorizzazione o un altro potenziale blocco e spero che un secondo paio di occhi possa scoprire qualcosa.
Riley C

Era un po 'impegnato, quindi il ritardo - proverò a dedicare del tempo a capirlo. Dopo una rapida occhiata, sembra che CallsManager costruisca HeadsetMediaButton. Il callback della sessione dovrebbe occuparsi di chiamare handleHeadsetHook (KeyEvent) su callback da MediaSessionManager. Tutto il codice sembra corrispondere ... ma mi chiedo, qualcuno potrebbe rimuovere l'intento KeyEvent.ACTION_DOWN di testare? (Cioè, attiva il KeyEvent.ACTION_UP solo una volta.)
Valter Jansons,

In realtà, HeadsetMediaButton funziona con MediaSession e non interagisce direttamente con MediaSessionManager ...
Valter Jansons

1
Per quelli di voi che sono riusciti a trovare una situazione in cui il metodo originale non sembra funzionare (ad esempio Lollipop ha problemi), ho buone e cattive notizie: sono riuscito a far funzionare ACTION_UP al 100%, nel mio codice, con un FULL_WAKE_LOCK. Non funzionerà con PARTIAL_WAKE_LOCK. Non c'è assolutamente alcuna documentazione sul perché di questo. Lo descriverò in dettaglio in una risposta futura quando testerò il mio codice esperimento in modo più approfondito. La cattiva notizia è, ovviamente, che FULL_WAKE_LOCK è deprecato, quindi questa è una correzione che durerà solo finché Google la manterrà nell'API.
leRobot

1
Il problema della risposta originale non viene chiamato in molti casi. Ho trovato meglio chiamare prima exec e poi chiamare comunque il pulsante su e giù subito dopo.
Warpzit

36

La soluzione completamente funzionante si basa sul codice @Valter Strods.

Per farlo funzionare, devi visualizzare un'attività (invisibile) sulla schermata di blocco in cui viene eseguito il codice.

AndroidManifest.xml

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />

<activity android:name="com.mysms.android.lib.activity.AcceptCallActivity"
        android:launchMode="singleTop"
        android:excludeFromRecents="true"
        android:taskAffinity=""
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:theme="@style/Mysms.Invisible">
    </activity>

Attività di accettazione della chiamata

package com.mysms.android.lib.activity;

import android.app.Activity;
import android.app.KeyguardManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.view.WindowManager;

import org.apache.log4j.Logger;

import java.io.IOException;

public class AcceptCallActivity extends Activity {

     private static Logger logger = Logger.getLogger(AcceptCallActivity.class);

     private static final String MANUFACTURER_HTC = "HTC";

     private KeyguardManager keyguardManager;
     private AudioManager audioManager;
     private CallStateReceiver callStateReceiver;

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);

         keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
         audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
     }

     @Override
     protected void onResume() {
         super.onResume();

         registerCallStateReceiver();
         updateWindowFlags();
         acceptCall();
     }

     @Override
     protected void onPause() {
         super.onPause();

         if (callStateReceiver != null) {
              unregisterReceiver(callStateReceiver);
              callStateReceiver = null;
         }
     }

     private void registerCallStateReceiver() {
         callStateReceiver = new CallStateReceiver();
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
         registerReceiver(callStateReceiver, intentFilter);
     }

     private void updateWindowFlags() {
         if (keyguardManager.inKeyguardRestrictedInputMode()) {
              getWindow().addFlags(
                       WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
                                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON |
                                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
         } else {
              getWindow().clearFlags(
                       WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
                                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
                                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
         }
     }

     private void acceptCall() {

         // for HTC devices we need to broadcast a connected headset
         boolean broadcastConnected = MANUFACTURER_HTC.equalsIgnoreCase(Build.MANUFACTURER)
                  && !audioManager.isWiredHeadsetOn();

         if (broadcastConnected) {
              broadcastHeadsetConnected(false);
         }

         try {
              try {
                  logger.debug("execute input keycode headset hook");
                  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
                  logger.debug("send keycode headset hook intents");
                  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));

                  sendOrderedBroadcast(btnDown, enforcedPerm);
                  sendOrderedBroadcast(btnUp, enforcedPerm);
              }
         } finally {
              if (broadcastConnected) {
                  broadcastHeadsetConnected(false);
              }
         }
     }

     private void broadcastHeadsetConnected(boolean connected) {
         Intent i = new Intent(Intent.ACTION_HEADSET_PLUG);
         i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
         i.putExtra("state", connected ? 1 : 0);
         i.putExtra("name", "mysms");
         try {
              sendOrderedBroadcast(i, null);
         } catch (Exception e) {
         }
     }

     private class CallStateReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
              finish();
         }
     }
}

Stile

<style name="Mysms.Invisible">
    <item name="android:windowFrame">@null</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowContentOverlay">@null</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowAnimationStyle">@null</item>
</style>

Finalmente chiama la magia!

Intent intent = new Intent(context, AcceptCallActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
context.startActivity(intent);

1
dove mi suppoz di aggiungere il codice sotto "Infine chiama la magia". Funzionerà per Android 6.0
Akshay Shah

Sono venuto qui per dire che broadcastHeadsetConnected (boolean connected) è ciò che ha risolto un problema in un dispositivo Samsung A3 2016. Senza di ciò, un metodo molto simile (utilizzando attività separate e trasparenti e thread per invocazioni e quant'altro) funzionava completamente per circa 20 dispositivi testati, quindi è arrivato questo A3 e mi ha costretto a ricontrollare questa domanda per nuove risposte. Dopo il confronto con il mio codice, questa è stata la differenza significativa!
leRobot

1
Come posso rifiutare anche una chiamata? Puoi aggiornare la risposta per dimostrarlo?
Amanni

@leRobot Questa risposta controlla se si tratta di un dispositivo HTC da trasmettereHeadsetConnected, come puoi verificare se si tratta di un dispositivo Samsung A3 2016? A proposito, questa è davvero una buona risposta, la mia app può rispondere alla telefonata anche se lo schermo è bloccato.
eepty

@eepty È possibile utilizzare il riferimento ufficiale del dispositivo per i dati di build. ( support.google.com/googleplay/answer/1727131?hl=it ). Uso Build.MODEL.startsWith ("SM-A310") per l'A3 2016. MA! Posso confermare che l'A3 2016 non supporta le trasmissioni connesse con le cuffie! Ciò che ha effettivamente risolto il mio problema è stato modificare l'ordine in modo che Runtime.getRuntime (). Exec (... si attivi per primo per questi dispositivi. Sembra funzionare ogni volta per quel dispositivo e non ricade nell'eccezione.
leRobot

14

Quello che segue è un approccio alternativo che ha funzionato per me. Invia l'evento chiave al server di telecomunicazioni direttamente utilizzando l'API MediaController. Ciò richiede che l'app disponga dell'autorizzazione BIND_NOTIFICATION_LISTENER_SERVICE e che venga concessa esplicita concessione dell'accesso alle notifiche da parte dell'utente:

@TargetApi(Build.VERSION_CODES.LOLLIPOP) 
void sendHeadsetHookLollipop() {
    MediaSessionManager mediaSessionManager =  (MediaSessionManager) getApplicationContext().getSystemService(Context.MEDIA_SESSION_SERVICE);

    try {
        List<MediaController> mediaControllerList = mediaSessionManager.getActiveSessions 
                     (new ComponentName(getApplicationContext(), NotificationReceiverService.class));

        for (MediaController m : mediaControllerList) {
             if ("com.android.server.telecom".equals(m.getPackageName())) {
                 m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));
                 log.info("HEADSETHOOK sent to telecom server");
                 break;
             }
        }
    } catch (SecurityException e) {
        log.error("Permission error. Access to notification not granted to the app.");      
    }  
}

NotificationReceiverService.class nel codice sopra potrebbe essere solo una classe vuota.

import android.service.notification.NotificationListenerService;

public class NotificationReceiverService extends NotificationListenerService{
     public NotificationReceiverService() {
     }
}

Con la sezione corrispondente nel manifest:

    <service android:name=".NotificationReceiverService" android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
        android:enabled="true" android:exported="true">
    <intent-filter>
         <action android:name="android.service.notification.NotificationListenerService" />
    </intent-filter>

Poiché l'obiettivo dell'evento è esplicito, questo dovrebbe probabilmente evitare qualsiasi effetto collaterale dell'attivazione del lettore multimediale.

Nota: il server di telecomunicazioni potrebbe non essere immediatamente attivo dopo lo squillo. Affinché funzioni in modo affidabile, potrebbe essere utile per l'app implementare MediaSessionManager.OnActiveSessionsChangedListener per monitorare quando il server di telecomunicazioni diventa attivo, prima di inviare l'evento.

Aggiornare:

In Android O , è necessario simulare ACTION_DOWNprima ACTION_UP, altrimenti quanto sopra non ha effetto. cioè è necessario quanto segue:

m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));

Ma poiché una chiamata ufficiale per rispondere alla chiamata è disponibile da Android O (vedi risposta in alto), potrebbe non esserci più bisogno di questo hack, a meno che non si sia bloccati con un vecchio livello API di compilazione prima di Android O.


Non ha funzionato per me. Ha restituito un errore di autorizzazione. Accesso alla notifica non concesso all'app. Sto usando Android L
Jame

2
Ciò richiede un passaggio aggiuntivo di concessione esplicita dell'autorizzazione da parte dell'utente, da qualche parte nel menu delle impostazioni a seconda del sistema, oltre ad accettare l'autorizzazione nel manifest.
headuck

riportare questo sembra funzionare in un caso ristretto: Galaxy A3 2016 con Marshmallow. Lo testerò in un gruppo di dispositivi A3 che non funzionano con il metodo keyevent di input a causa di un'ECCEZIONE FATALE: java.lang.SecurityException: l'iniezione in un'altra applicazione richiede l'autorizzazione INJECT_EVENTS. I dispositivi offensivi sono circa il 2% della mia base di utenti e non sto replicando la loro eccezione, ma proverò questo metodo per vedere se riescono a rispondere alla chiamata. Fortunatamente la mia app richiede già una notifica esplicita. accesso per altri scopi.
leRobot

dopo numerosi test, sono lieto di segnalare che i dispositivi A3 2016 che non funzionavano con l'exec "input keyevent" sono riusciti a funzionare con il metodo MediaController # dispatchMediaButtonEvent (<hook KeryEvent>)). questo ovviamente funziona solo dopo che l'utente consente l'accesso esplicito alle notifiche, quindi dovrai aggiungere una schermata che indirizza alle Impostazioni Android per quello, e in pratica hai bisogno che l'utente intraprenda un'azione extra per questo, come dettagliato nella risposta. Nella mia app abbiamo adottato ulteriori passaggi per continuare a chiederlo se l'utente va su quella schermata ma non aggiunge notifica. accesso
leRobot

Funziona su Android Nougat. La soluzione di @notz funziona benissimo altrimenti, ma si lamenta "Solo il sistema può inviare l'evento chiave multimediale alla sessione con priorità globale" su Android 7.
PB

9

Per elaborare un po 'la risposta di @Muzikant e modificarla un po' per funzionare in modo un po 'più pulito sul mio dispositivo, prova input keyevent 79la costante per KeyEvent.KEYCODE_HEADSETHOOK . Molto approssimativamente:

    new Thread(new Runnable() {

        @Override
        public void run() {

            try {

                Runtime.getRuntime().exec( "input keyevent " + KeyEvent.KEYCODE_HEADSETHOOK );
            }
            catch (Throwable t) {

                // do something proper here.
            }
        }
    }).start();

Perdonate le convenzioni di codifica piuttosto pessime, non sono molto esperto nelle chiamate Runtime.exec (). Nota che il mio dispositivo non è rootato, né sto richiedendo privilegi di root.

Il problema con questo approccio è che funziona solo in determinate condizioni (per me). Cioè, se eseguo il thread sopra da un'opzione di menu che l'utente seleziona mentre una chiamata sta squillando, la chiamata risponde perfettamente. Se lo eseguo da un ricevitore che monitora lo stato della chiamata in arrivo, viene completamente ignorato.

Quindi sul mio Nexus 5 funziona bene per la risposta guidata dall'utente e dovrebbe adattarsi allo scopo di una schermata di chiamata personalizzata. Semplicemente non funzionerà per nessun tipo di applicazione automatizzata di controllo delle chiamate.

Da notare anche tutti i possibili avvertimenti, incluso che anche questo probabilmente smetterà di funzionare in un aggiornamento o due.


input keyevent 79funziona bene su Sony Xperia 5.0. Funziona quando si chiama da un'attività o da un ricevitore di trasmissione.
nicolas

0

tramite i comandi adb Come rispondere a una chiamata da adb

Tieni presente che Android è Linux con un'enorme JVM sul front-end. Puoi scaricare un'app della riga di comando e eseguire il root del telefono e ora hai un normale computer Linux e una riga di comando che fa tutte le cose normali. Esegui script, puoi persino eseguire ssh (trucco OpenVPN)


0

Grazie @notz la risposta di sta funzionando per me sul Lolillop. Per mantenere questo codice funzionante con il vecchio SDK Android, puoi eseguire questo codice:

if (Build.VERSION.SDK_INT >= 21) {  
    Intent answerCalintent = new Intent(context, AcceptCallActivity.class);  
    answerCalintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 
                             Intent.FLAG_ACTIVITY_CLEAR_TASK  | 
                             Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    context.startActivity(answerCalintent);  
}  
else {  
  if (telephonyService != null) {  
    try {  
        telephonyService.answerRingingCall();  
    }  
    catch (Exception e) {  
        answerPhoneHeadsethook();  
    }  
  }  
}  

0

Come attivare l'altoparlante dopo aver risposto automaticamente alle chiamate.

Ho risolto il mio problema sopra con setSpeakerphoneOn. Penso che valga la pena pubblicarlo qui, poiché il caso d'uso per la risposta automatica a una telefonata spesso richiederebbe anche il vivavoce per essere utile. Grazie ancora a tutti su questo thread, che lavoro fantastico.

Questo funziona per me su Android 5.1.1 sul mio Nexus 4 senza ROOT. ;)

Autorizzazione richiesta:

<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>

Codice Java:

// this means the phone has answered
if(state==TelephonyManager.CALL_STATE_OFFHOOK)
{
    // try and turn on speaker phone
    final Handler mHandler = new Handler();
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            AudioManager audioManager = (AudioManager) localContext.getSystemService(Context.AUDIO_SERVICE);

            // this doesnt work without android.permission.MODIFY_PHONE_STATE
            // audioManager.setMode(AudioManager.MODE_IN_CALL);

            // weirdly this works
            audioManager.setMode(AudioManager.MODE_NORMAL); // this is important
            audioManager.setSpeakerphoneOn(true);

            // note the phone interface won't show speaker phone is enabled
            // but the phone speaker will be on
            // remember to turn it back off when your done ;)
        }
    }, 500); // half a second delay is important or it might fail
}

1
Interessante. In realtà sto cercando di rispondere alla chiamata e accendere l'altoparlante insieme, quindi questo approccio sembra risolvere entrambi :). Ho una domanda simile ad alcuni dei commenti in altre risposte: dove va a finire questo codice?
fangmobile

-1

Esegui il seguente comando come root:

input keyevent 5

Maggiori dettagli sulla simulazione di eventi chiave qui .

Puoi utilizzare questa classe base che ho creato per eseguire comandi come root dalla tua app.


1
Durante il test con un profilo utente normale, questo mi ha mostrato l'interfaccia utente durante la chiamata, chiedendomi di scorrere a sinistra / destra per rifiutare / rispondere o utilizzare un'azione / risposta rapida. Se l'OP sta creando una schermata di chiamata in arrivo personalizzata , questo non è di alcun aiuto a meno che non si comporti in modo diverso sotto root, cosa che dubito come se non si comportasse bene per un utente normale, la chiamata probabilmente fallirebbe e non attivare un'azione diversa.
Valter Jansons

-2

prova questo: prima aggiungi i permessi quindi usa killCall () per riagganciare usa answerCall () per rispondere alla chiamata

<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"></uses-permission>


public void killCall() {
    try {
        TelephonyManager telephonyManager =
                (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);

        Class classTelephony = Class.forName(telephonyManager.getClass().getName());
        Method methodGetITelephony = classTelephony.getDeclaredMethod("getITelephony");

        methodGetITelephony.setAccessible(true);

        Object telephonyInterface = methodGetITelephony.invoke(telephonyManager);

        Class telephonyInterfaceClass =
                Class.forName(telephonyInterface.getClass().getName());
        Method methodEndCall = telephonyInterfaceClass.getDeclaredMethod("endCall");

        methodEndCall.invoke(telephonyInterface);

    } catch (Exception ex) {
        Log.d(TAG, "PhoneStateReceiver **" + ex.toString());
    }
}

public void answerCall() {
    try {
        Runtime.getRuntime().exec("input keyevent " +
                Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK));

    } catch (IOException e) {
        answerRingingCallWithIntent();
    }
}

public void answerRingingCallWithIntent() {
    try {
        Intent localIntent1 = new Intent(Intent.ACTION_HEADSET_PLUG);
        localIntent1.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
        localIntent1.putExtra("state", 1);
        localIntent1.putExtra("microphone", 1);
        localIntent1.putExtra("name", "Headset");
        getContext().sendOrderedBroadcast(localIntent1, "android.permission.CALL_PRIVILEGED");

        Intent localIntent2 = new Intent(Intent.ACTION_MEDIA_BUTTON);
        KeyEvent localKeyEvent1 = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK);
        localIntent2.putExtra(Intent.EXTRA_KEY_EVENT, localKeyEvent1);
        getContext().sendOrderedBroadcast(localIntent2, "android.permission.CALL_PRIVILEGED");

        Intent localIntent3 = new Intent(Intent.ACTION_MEDIA_BUTTON);
        KeyEvent localKeyEvent2 = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK);
        localIntent3.putExtra(Intent.EXTRA_KEY_EVENT, localKeyEvent2);
        getContext().sendOrderedBroadcast(localIntent3, "android.permission.CALL_PRIVILEGED");

        Intent localIntent4 = new Intent(Intent.ACTION_HEADSET_PLUG);
        localIntent4.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
        localIntent4.putExtra("state", 0);
        localIntent4.putExtra("microphone", 1);
        localIntent4.putExtra("name", "Headset");
        getContext().sendOrderedBroadcast(localIntent4, "android.permission.CALL_PRIVILEGED");
    } catch (Exception e2) {
        e2.printStackTrace();
    }
}

-2

Cordiali saluti, se sei interessato a come TERMINARE una chiamata in corso su Android O, Valter's Method 1: TelephonyManager.answerRingingCall()funziona se cambi il metodo che invoca endCall.

Richiede solo il android.permission.CALL_PHONEpermesso.

Ecco il codice:

// set the logging tag constant; you probably want to change this
final String LOG_TAG = "TelephonyAnswer";

public void endCall() {
    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("endCall").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);
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.