¿Cómo se pueden responder las llamadas entrantes mediante programación en Android 5.0 (Lollipop)?

87

Como estoy tratando de crear una pantalla personalizada para las llamadas entrantes, estoy tratando de responder programáticamente una llamada entrante. Estoy usando el siguiente código pero no funciona en 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");
maveroid
fuente
oh hombre, ¿por qué encontrarse con esto? me parece más fácil \ m /
nobalG
Estoy creando una pantalla de llamadas entrantes personalizada para usuarios de Android.
maveroid
2
¿Nadie? ¡También me interesa este! Intenté muchas cosas, pero no funcionaron: /
Arthur
1
@nobalG está diciendo programáticamente
dsharew
1
@maveroid, ¿se te ocurrió una solución para Android 5.0?
Artursfreire

Respuestas:

155

Actualizar con Android 8.0 Oreo

A pesar de que la pregunta se hizo originalmente para la compatibilidad con Android L, la gente todavía parece estar respondiendo a esta pregunta y respuesta, por lo que vale la pena describir las mejoras introducidas en Android 8.0 Oreo. Los métodos compatibles con versiones anteriores todavía se describen a continuación.

¿Qué cambió?

A partir de Android 8.0 Oreo , el grupo de permisos PHONE también contiene el permiso ANSWER_PHONE_CALLS . Como sugiere el nombre del permiso, mantenerlo permite que su aplicación acepte llamadas entrantes mediante programación a través de una llamada API adecuada sin ningún tipo de piratería en el sistema mediante la reflexión o la simulación del usuario.

¿Cómo utilizamos este cambio?

Debe verificar la versión del sistema en tiempo de ejecución si admite versiones anteriores de Android para poder encapsular esta nueva llamada a la API mientras mantiene la compatibilidad con esas versiones anteriores de Android. Debe seguir solicitando permisos en tiempo de ejecución para obtener ese nuevo permiso durante el tiempo de ejecución, como es estándar en las versiones más nuevas de Android.

Después de haber obtenido el permiso, su aplicación simplemente tiene que llamar al método acceptRingingCall de TelecomManager . Entonces, una invocación básica tiene el siguiente aspecto:

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();

Método 1: TelephonyManager.answerRingingCall ()

Para cuando tienes un control ilimitado sobre el dispositivo.

¿Que es esto?

Existe TelephonyManager.answerRingingCall () que es un método interno oculto. Funciona como un puente para ITelephony.answerRingingCall () que se ha discutido en las interwebs y parece prometedor al principio. Es no disponibles en 4.4.2_r1 ya que sólo se introdujo en comprometerse 83da75d para Android 4.4 KitKat ( línea 1537 en 4.4.3_r1 ) y más tarde "reintroducido" en comprometerse f1e1e77 de Lollipop ( línea 3138 en 5.0.0_r1 ) debido a la forma en que el El árbol de Git estaba estructurado. Esto significa que, a menos que solo admita dispositivos con Lollipop, que probablemente sea una mala decisión en función de la pequeña participación de mercado que tiene en este momento, aún debe proporcionar métodos de respaldo si sigue esta ruta.

¿Cómo usaríamos esto?

Como el método en cuestión está oculto para el uso de las aplicaciones del SDK, debe usar la reflexión para examinar y usar dinámicamente el método durante el tiempo de ejecución. Si no está familiarizado con la reflexión, puede leer rápidamente ¿Qué es la reflexión y por qué es útil? . También puede profundizar en los detalles en Trail: The Reflection API si está interesado en hacerlo.

¿Y cómo se ve eso en el código?

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

¡Esto es demasiado bueno para ser verdad!

De hecho, hay un pequeño problema. Este método debería ser completamente funcional, pero el administrador de seguridad quiere que las personas que llaman tengan android.permission.MODIFY_PHONE_STATE . Este permiso está en el ámbito de las funciones del sistema que están parcialmente documentadas, ya que no se espera que terceros lo toquen (como puede ver en la documentación correspondiente). Puede intentar agregar un <uses-permission>para él, pero eso no servirá de nada porque el nivel de protección para este permiso es firma | sistema ( consulte la línea 1201 de core / AndroidManifest en 5.0.0_r1 ).

Puede leer el Problema 34785: Actualización de android: documentación de nivel de protección que se creó en 2012 para ver que nos faltan detalles sobre la "sintaxis de tubería" específica, pero al experimentar, parece que debe funcionar como un 'Y' que significa todo el las banderas especificadas deben cumplirse para que se otorgue el permiso. Trabajando bajo esa suposición, significaría que debe tener su aplicación:

  1. Instalado como una aplicación del sistema.

    Esto debería estar bien y podría lograrse pidiendo a los usuarios que instalen usando un ZIP en la recuperación, como al rootear o instalar aplicaciones de Google en ROM personalizadas que no las tienen ya empaquetadas.

  2. Firmado con la misma firma que frameworks / base, también conocido como sistema, también conocido como ROM.

    Aquí es donde surgen los problemas. Para hacer esto, necesita tener en sus manos las claves utilizadas para firmar frameworks / base. No solo tendría que acceder a las claves de Google para las imágenes de fábrica de Nexus, sino que también tendría que acceder a las claves de todos los demás fabricantes de equipos originales y desarrolladores de ROM. Esto no parece plausible, por lo que puede firmar su aplicación con las claves del sistema, ya sea creando una ROM personalizada y pidiéndoles a sus usuarios que la cambien (lo que puede ser difícil) o encontrando un exploit con el que se pueda omitir el nivel de protección de permisos. (que también puede ser difícil).

Además, este comportamiento parece estar relacionado con el Problema 34792: Android Jelly Bean / 4.1: android.permission.READ_LOGS ya no funciona, lo que también utiliza el mismo nivel de protección junto con una marca de desarrollo no documentada.

Trabajar con TelephonyManager suena bien, pero no funcionará a menos que obtenga el permiso apropiado, lo cual no es tan fácil de hacer en la práctica.

¿Qué pasa con el uso de TelephonyManager de otras formas?

Lamentablemente, parece que es necesario que tengas el permiso android.permission.MODIFY_PHONE_STATE para usar las herramientas geniales, lo que a su vez significa que tendrás dificultades para acceder a esos métodos.


Método 2: llamada de servicio CÓDIGO DE SERVICIO

Para cuando pueda probar que la compilación que se ejecuta en el dispositivo funcionará con el código especificado.

Sin poder interactuar con el TelephonyManager, también existe la posibilidad de interactuar con el servicio a través del serviceejecutable.

¿Como funciona esto?

Es bastante simple, pero hay menos documentación sobre esta ruta que sobre otras. Sabemos con certeza que el ejecutable tiene dos argumentos: el nombre del servicio y el código.

  • El nombre del servicio que queremos usar es teléfono .

    Esto se puede ver ejecutando service list.

  • El código que queremos usar parece haber sido 6, pero ahora parece ser 5 .

    Parece que se ha basado en IBinder.FIRST_CALL_TRANSACTION + 5 para muchas versiones ahora (de 1.5_r4 a 4.4.4_r1 ) pero durante las pruebas locales, el código 5 funcionó para responder una llamada entrante. Como Lollipo es una actualización masiva en todos lados, es comprensible que los componentes internos también hayan cambiado aquí.

Esto resulta con un comando de service call phone 5.

¿Cómo utilizamos esto programáticamente?

Java

El siguiente código es una implementación aproximada hecha para funcionar como prueba de concepto. Si realmente desea seguir adelante y utilizar este método, probablemente desee consultar las pautas para el uso de su sin problemas y posiblemente cambiar al libsuperuser más desarrollado por 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();
}

Manifiesto

<!-- Inform the user we want them root accesses. -->
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>

¿Esto realmente requiere acceso de root?

Lamentablemente, eso parece. Puede intentar usar Runtime.exec en él, pero no pude tener suerte con esa ruta.

¿Qué tan estable es esto?

Me alegro de que lo hayas preguntado. Debido a que no está documentado, esto puede afectar a varias versiones, como lo ilustra la aparente diferencia de código anterior. El nombre del servicio probablemente debería permanecer en el teléfono en varias compilaciones, pero por lo que sabemos, el valor del código puede cambiar en varias compilaciones de la misma versión (modificaciones internas por, digamos, el skin del OEM) a su vez, rompiendo el método utilizado. Por lo tanto, vale la pena mencionar que las pruebas se realizaron en un Nexus 4 (mako / occam). Personalmente, le aconsejaría que no utilice este método, pero como no puedo encontrar un método más estable, creo que esta es la mejor opción.


Método original: Intentos de código clave de auricular

Para momentos en los que tienes que conformarte.

En la siguiente sección se vio fuertemente influenciado por esta respuesta por Riley C .

El método de intención de auricular simulado que se publicó en la pregunta original parece transmitirse tal como se esperaría, pero no parece lograr el objetivo de responder la llamada. Si bien parece haber un código en su lugar que debería manejar esos intentos, simplemente no se les importa, lo que significa que debe haber algún tipo de contramedidas nuevas contra este método. El registro tampoco muestra nada de interés y personalmente no creo que valga la pena buscar en la fuente de Android para esto solo debido a la posibilidad de que Google introduzca un pequeño cambio que rompe fácilmente el método utilizado de todos modos.

¿Hay algo que podamos hacer ahora mismo?

El comportamiento se puede reproducir de forma coherente utilizando el ejecutable de entrada. Toma un argumento de código clave , para el cual simplemente pasamos KeyEvent.KEYCODE_HEADSETHOOK . El método ni siquiera requiere acceso de root, lo que lo hace adecuado para casos de uso comunes en el público en general, pero hay un pequeño inconveniente en el método: el evento de presión del botón del auricular no se puede especificar para requerir un permiso, lo que significa que funciona como un verdadero pulsa el botón y sube a lo largo de toda la cadena, lo que a su vez significa que debes tener cuidado sobre cuándo simular la pulsación del botón, ya que podría, por ejemplo, activar el reproductor de música para que comience la reproducción si nadie más de mayor prioridad está listo para manejar el evento.

¿Código?

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

Hay una buena API pública para Android 8.0 Oreo y versiones posteriores.

No existe una API pública anterior a Android 8.0 Oreo. Las API internas están prohibidas o simplemente sin documentación. Debe proceder con precaución.

Valter Jansons
fuente
Con respecto a las intenciones del código clave de los auriculares, ¿ha consultado la fuente de Google aquí por alguna razón por la que dejarían de actuar? Lo curioso es que las llamadas aún se pueden rechazar fácilmente con estos intentos (solo emule una pulsación larga), pero nada funciona para responder. Todavía tengo que encontrar una verificación de permiso explícita u otro bloqueo potencial y espero que un segundo par de ojos descubra algo.
Riley C
Estaba un poco ocupado, por lo tanto, el retraso; trataré de pasar un tiempo averiguando esto. Después de un vistazo rápido, parece que CallsManager construye HeadsetMediaButton. La devolución de llamada de la sesión allí debería encargarse de llamar a handleHeadsetHook (KeyEvent) en las devoluciones de llamada de MediaSessionManager. Todo el código parece coincidir ... pero me pregunto, ¿alguien podría eliminar la intención de prueba KeyEvent.ACTION_DOWN? (Es decir, solo active KeyEvent.ACTION_UP una sola vez).
Valter Jansons
En realidad, HeadsetMediaButton funciona con MediaSession y no interactúa directamente con MediaSessionManager ...
Valter Jansons
1
Para aquellos de ustedes que lograron encontrar una situación en la que el Método Original no parece funcionar (por ejemplo, Lollipop tiene problemas), tengo buenas y malas noticias: he logrado que ACTION_UP funcione al 100%, en mi código, con un FULL_WAKE_LOCK. No funcionará con PARTIAL_WAKE_LOCK. No hay absolutamente ninguna documentación sobre el por qué de esto. Detallaré esto en una respuesta futura cuando pruebe mi código de experimento de manera más extensa. La mala noticia es, por supuesto, que FULL_WAKE_LOCK está obsoleto, por lo que esta es una solución que solo durará mientras Google la mantenga en la API.
leRobot
1
La captura de la respuesta original no se llama en muchos casos. Me pareció mejor llamar al ejecutivo primero y luego llamar al botón de arriba hacia abajo de todos modos inmediatamente después.
Warpzit
36

La solución completamente funcional se basa en el código de @Valter Strods.

Para que funcione, debe mostrar una actividad (invisible) en la pantalla de bloqueo donde se ejecuta el código.

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>

Actividad de aceptación de llamadas

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

Estilo

<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 llama a 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);
notz
fuente
1
donde mi suppoz agregar el código debajo de "Finalmente llamar a la magia". ¿Funcionará para Android 6.0
Akshay Shah
Vine aquí para decir que broadcastHeadsetConnected (booleano conectado) es lo que resolvió un problema en un dispositivo Samsung A3 2016. Sin eso, un método muy similar (usando actividad separada y transparente, e hilos para invocaciones y demás) estaba funcionando completamente para aproximadamente 20 dispositivos probados, luego apareció este A3 y me obligó a volver a verificar esta pregunta para obtener nuevas respuestas. Después de comparar con mi código, ¡esa fue la diferencia significativa!
leRobot
1
¿Cómo puedo rechazar una llamada también? ¿Puedes actualizar la respuesta para mostrar esto?
Amanni
@leRobot Esta respuesta comprueba si es un dispositivo HTC para broadcastHeadsetConnected, ¿cómo puedes comprobar si es un dispositivo Samsung A3 2016? Por cierto, esta es realmente una buena respuesta, mi aplicación puede responder la llamada telefónica incluso si la pantalla está bloqueada.
Eepty
@eepty Puede usar la referencia oficial del dispositivo para los datos de compilación. ( support.google.com/googleplay/answer/1727131?hl=es ). Yo uso Build.MODEL.startsWith ("SM-A310") para el A3 2016. ¡PERO! Puedo confirmar que el A3 2016 no admite transmisiones conectadas a auriculares. Lo que realmente resolvió mi problema fue cambiar el orden para que Runtime.getRuntime (). Exec (... se active primero para estos dispositivos. Parece funcionar siempre para ese dispositivo y no vuelve a la excepción.
leRobot
14

El siguiente es un enfoque alternativo que funcionó para mí. Envía el evento clave al servidor de telecomunicaciones directamente usando la API MediaController. Esto requiere que la aplicación tenga el permiso BIND_NOTIFICATION_LISTENER_SERVICE y que el usuario conceda explícitamente acceso a las notificaciones:

@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 en el código anterior podría ser solo una clase vacía.

import android.service.notification.NotificationListenerService;

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

Con la sección correspondiente en el manifiesto:

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

Dado que el objetivo del evento es explícito, esto probablemente debería evitar cualquier efecto secundario de activar el reproductor multimedia.

Nota: es posible que el servidor de telecomunicaciones no esté activo inmediatamente después del evento de llamada. Para que esto funcione de manera confiable, puede ser útil que la aplicación implemente MediaSessionManager.OnActiveSessionsChangedListener para monitorear cuando el servidor de telecomunicaciones se activa, antes de enviar el evento.

Actualizar:

En Android O , es necesario simular ACTION_DOWNantes ACTION_UP, de lo contrario, lo anterior no tiene ningún efecto. es decir, se necesita lo siguiente:

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

Pero dado que una llamada oficial para contestar llamadas está disponible desde Android O (ver respuesta superior), puede que ya no sea necesario este truco, a menos que uno esté atascado con un nivel de API de compilación anterior antes de Android O.

cabezazo
fuente
No funciono para mí. Devolvió el error de permiso. Acceso a notificación no otorgado a la aplicación. Estoy usando Android L
Jame
2
Esto requiere un paso adicional de otorgar explícitamente el permiso por parte del usuario, en algún lugar del menú de configuración según el sistema, además de aceptar el permiso en el manifiesto.
headuck
informar de esto parece funcionar en un caso estrecho: Galaxy A3 2016 con Marshmallow. Probaré esto en un grupo de dispositivos A3 que no funcionan con el método input keyevent debido a una EXCEPCIÓN FATAL: java.lang.SecurityException: la inyección en otra aplicación requiere el permiso INJECT_EVENTS. Los dispositivos infractores son aproximadamente el 2% de mi base de usuarios y no estoy replicando su excepción, pero intentaré este método para ver si logran atender la llamada. Afortunadamente, mi aplicación ya está solicitando notif. acceso para otros fines.
leRobot
Después de pruebas exhaustivas, me complace informar que los dispositivos A3 2016 que estaban fallando con el "input keyevent" ejecutivo lograron funcionar con el método MediaController # dispatchMediaButtonEvent (<hook KeryEvent>)). obviamente, esto solo funciona después de que el usuario permite el acceso de notificación explícito, por lo que tendrá que agregar una pantalla que dirija la configuración de Android para eso, y básicamente necesita que el usuario realice una acción adicional para esto, como se detalla en la respuesta. En mi aplicación, hemos tomado medidas adicionales para seguir preguntando si el usuario va a esa pantalla pero no agrega notif. acceso
leRobot
Esto funciona en Android Nougat. La solución de @notz funciona muy bien por lo demás, pero se queja "Solo el sistema puede enviar eventos de clave de medios a la sesión de prioridad global" en Android 7.
Peng Bai
9

Para desarrollar un poco la respuesta de @Muzikant, y modificarla un poco para que funcione un poco más limpio en mi dispositivo, pruebe input keyevent 79la constante de KeyEvent.KEYCODE_HEADSETHOOK . Muy aproximadamente:

    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();

Perdone las convenciones de codificación bastante malas, no estoy muy versado en las llamadas a Runtime.exec (). Tenga en cuenta que mi dispositivo no está rooteado ni estoy solicitando privilegios de root.

El problema con este enfoque es que solo funciona bajo ciertas condiciones (para mí). Es decir, si ejecuto el hilo anterior desde una opción de menú que el usuario selecciona mientras suena una llamada, la llamada responde bien. Si lo ejecuto desde un receptor que monitorea el estado de las llamadas entrantes, se ignora por completo.

Por lo tanto, en mi Nexus 5 funciona bien para la respuesta dirigida por el usuario y debería adaptarse al propósito de una pantalla de llamada personalizada. Simplemente no funcionará para ningún tipo de aplicaciones de control de llamadas automatizadas.

También se deben tener en cuenta todas las posibles advertencias, incluido que esto también probablemente dejará de funcionar en una actualización o dos.

Riley C
fuente
input keyevent 79funciona bien en Sony Xperia 5.0. Funciona al llamar desde una actividad o desde un receptor de transmisión.
nicolas
0

a través de los comandos adb Cómo atender una llamada por adb

Tenga en cuenta que Android es Linux con una JVM masiva en la interfaz. Puede descargar una aplicación de línea de comandos y rootear el teléfono y ahora tiene una computadora Linux normal y una línea de comandos que hace todas las cosas normales. Ejecute scripts, incluso puede usar ssh (truco de OpenVPN)

usuario1544207
fuente
0

Gracias @notz, la respuesta de está funcionando para mí en Lolillop. Para mantener este código funcionando con el antiguo SDK de Android, puede hacer este código:

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();  
    }  
  }  
}  
Khac Quyet Dang
fuente
0

Cómo encender el teléfono con altavoz después de responder llamadas automáticamente.

Resolví mi problema anterior con setSpeakerphoneOn. Creo que vale la pena publicarlo aquí, ya que el caso de uso para responder automáticamente una llamada telefónica a menudo también requeriría que el altavoz sea útil. Gracias de nuevo a todos en este hilo, qué trabajo tan maravilloso.

Esto me funciona en Android 5.1.1 en mi Nexus 4 sin ROOT. ;)

Permiso requerido:

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

Código 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
}
Madhava Jay
fuente
1
Interesante. De hecho, estoy tratando de responder una llamada y encender el altavoz juntos, por lo que este enfoque parece resolver ambos :). Sin embargo, tengo una pregunta similar a algunos de los comentarios en otras respuestas: ¿a dónde va este código?
fangmobile
-1

Ejecute el siguiente comando como root:

input keyevent 5

Más detalles sobre la simulación de eventos clave aquí .

Puede usar esta clase base que creé para ejecutar comandos como root desde su aplicación.

Muzikant
fuente
1
Mientras probaba con un perfil de usuario normal, esto me mostró la IU en llamada, pidiéndome que deslice el dedo hacia la izquierda / derecha para rechazar / responder o usar una acción / respuesta rápida. Si el OP está creando una pantalla de llamada entrante personalizada , esto no es realmente de ayuda a menos que se comporte de manera diferente bajo la raíz, lo cual dudo que no se comportara bien para un usuario normal, la llamada probablemente simplemente fallaría y no desencadenar una acción diferente.
Valter Jansons
-2

prueba esto: primero agrega los permisos y luego usa killCall () para colgar usa answerCall () para responder la llamada

<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();
    }
}
Miguel
fuente
-2

Para su información, si está interesado en cómo FINALIZAR una llamada en curso en Android O, Valter Method 1: TelephonyManager.answerRingingCall()funciona si cambia el método que invoca endCall.

Solo requiere el android.permission.CALL_PHONEpermiso.

Aquí está el código:

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