Wie können eingehende Anrufe in Android 5.0 (Lollipop) programmgesteuert beantwortet werden?


85

Während ich versuche, einen benutzerdefinierten Bildschirm für eingehende Anrufe zu erstellen, versuche ich, einen eingehenden Anruf programmgesteuert zu beantworten. Ich verwende den folgenden Code, aber er funktioniert nicht 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 man, warum rennst du da rein, schieb einfach man! scheint mir einfacher zu sein \ m /
nobalG

Ich erstelle einen benutzerdefinierten Bildschirm für eingehende Anrufe für Android-Benutzer.
Maveroid

2
Jemand? Ich interessiere mich auch für diesen! Versuchte viele Dinge, aber sie funktionierten nicht: /
Arthur

1
@nobalG sagt er programmatisch
dsharew

1
@maveroid, Hast du eine Problemumgehung für Android 5.0 gefunden?
Arthurfreire

Antworten:


153

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:

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

  2. 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 serviceausfü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.


Haben Sie in Bezug auf die Absichten des Headset-Keycodes die Google-Quelle hier aus irgendeinem Grund überprüft, warum sie nicht mehr bearbeitet werden? Das Lustige ist, dass Anrufe mit diesen Absichten immer noch leicht abgelehnt werden können (nur ein langes Drücken emulieren), aber nichts funktioniert, um zu antworten. Ich habe noch keine explizite Berechtigungsprüfung oder einen anderen potenziellen Block gefunden und hoffe, dass ein zweiter Satz Augen etwas aufdecken könnte.
Riley C

War ein bisschen beschäftigt, daher die Verzögerung - werde versuchen, einige Zeit damit zu verbringen, dies herauszufinden. Nach einem kurzen Blick scheint CallsManager HeadsetMediaButton zu erstellen. Der dortige Sitzungsrückruf sollte sich darum kümmern, handleHeadsetHook (KeyEvent) bei Rückrufen von MediaSessionManager aufzurufen. Der gesamte Code scheint übereinzustimmen ... aber ich frage mich, ob jemand die Testabsicht von KeyEvent.ACTION_DOWN entfernen könnte. (Das heißt, feuern Sie das KeyEvent.ACTION_UP nur einmal ab.)
Valter Jansons

Tatsächlich arbeitet HeadsetMediaButton mit der MediaSession und interagiert nicht direkt mit dem MediaSessionManager ...
Valter Jansons

1
Für diejenigen unter Ihnen, die es geschafft haben, eine Situation zu finden, in der die ursprüngliche Methode nicht funktioniert (z. B. hat Lollipop Probleme), habe ich gute und schlechte Nachrichten: Ich habe es geschafft, dass ACTION_UP in meinem Code zu 100% mit a funktioniert FULL_WAKE_LOCK. Es funktioniert nicht mit einem PARTIAL_WAKE_LOCK. Es gibt absolut keine Dokumentation darüber, warum dies so ist. Ich werde dies in einer zukünftigen Antwort detailliert beschreiben, wenn ich meinen Experimentcode ausführlicher teste. Die schlechte Nachricht ist natürlich, dass FULL_WAKE_LOCK veraltet ist. Dies ist also ein Fix, der nur so lange anhält, wie Google ihn in der API behält.
LeRobot

1
Der Haken aus der ursprünglichen Antwort wird in vielen Fällen nicht genannt. Ich habe es besser gefunden, zuerst exec anzurufen und dann gleich danach die Taste nach oben zu drücken.
Warpzit

36

Die voll funktionsfähige Lösung basiert auf @ Walter Strods-Code.

Damit es funktioniert, müssen Sie eine (unsichtbare) Aktivität auf dem Sperrbildschirm anzeigen, auf dem der Code ausgeführt wird.

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>

Rufen Sie Accept Activity an

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

Stil

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

Nennen Sie endlich die Magie!

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
wo mi suppoz den Code unter "Endlich die Magie nennen" hinzufügen. Funktioniert es für Android 6.0
Akshay Shah

Ich bin hierher gekommen, um zu sagen, dass BroadcastHeadsetConnected (boolean verbunden) ein Problem in einem Samsung A3 2016-Gerät gelöst hat. Ohne dies funktionierte eine sehr ähnliche Methode (mit separaten, transparenten Aktivitäten und Threads für Aufrufe und so weiter) für etwa 20 getestete Geräte vollständig. Dann kam dieser A3 und zwang mich, diese Frage erneut auf neue Antworten zu überprüfen. Nach dem Vergleich mit meinem Code war das der signifikante Unterschied!
LeRobot

1
Wie kann ich einen Anruf auch ablehnen? Können Sie die Antwort aktualisieren, um dies anzuzeigen?
Amanni

@leRobot Mit dieser Antwort wird überprüft, ob es sich um ein HTC-Gerät für BroadcastHeadsetConnected handelt. Wie können Sie überprüfen, ob es sich um ein Samsung A3 2016-Gerät handelt? Übrigens ist dies wirklich eine gute Antwort, meine App kann den Anruf entgegennehmen, auch wenn der Bildschirm gesperrt ist.
Leere

@eepty Sie können die offizielle Gerätereferenz für Build-Daten verwenden. ( support.google.com/googleplay/answer/1727131?hl=de ). Ich benutze Build.MODEL.startsWith ("SM-A310") für die A3 2016. ABER! Ich kann bestätigen, dass der A3 2016 keine mit Headsets verbundenen Sendungen unterstützt! Was mein Problem tatsächlich gelöst hat, war die Änderung der Reihenfolge, so dass Runtime.getRuntime (). Exec (... zuerst für diese Geräte ausgelöst wird. Es scheint jedes Mal für dieses Gerät zu funktionieren und greift nicht auf die Ausnahme zurück.
leRobot

14

Das Folgende ist ein alternativer Ansatz, der für mich funktioniert hat. Das Schlüsselereignis wird direkt über die MediaController-API an den Telekommunikationsserver gesendet. Dies erfordert , dass die App hat BIND_NOTIFICATION_LISTENER_SERVICE Erlaubnis und wird ausdrücklich Erteilung Mitteilung Zugriff vom Benutzer gegeben:

@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 im obigen Code könnte nur eine leere Klasse sein.

import android.service.notification.NotificationListenerService;

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

Mit dem entsprechenden Abschnitt im 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>

Da das Ziel des Ereignisses explizit ist, sollte dies wahrscheinlich jegliche Nebenwirkung des Auslösens des Media Players vermeiden.

Hinweis: Der Telekommunikationsserver ist möglicherweise nicht sofort nach dem Klingelereignis aktiv. Damit dies zuverlässig funktioniert, kann es für die App hilfreich sein , MediaSessionManager.OnActiveSessionsChangedListener zu implementieren, um zu überwachen, wann der Telekommunikationsserver aktiv wird, bevor das Ereignis gesendet wird.

Aktualisieren:

In Android O muss man ACTION_DOWNvorher simulieren ACTION_UP, sonst hat das oben genannte keine Auswirkung. dh Folgendes wird benötigt:

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

Da jedoch seit Android O ein offizieller Anruf zur Beantwortung eines Anrufs verfügbar ist (siehe obere Antwort), ist dieser Hack möglicherweise nicht mehr erforderlich, es sei denn, man hat vor Android O eine alte Kompilierungs-API-Stufe beibehalten.


Bei mir hat es nicht funktioniert. Es wurde ein Berechtigungsfehler zurückgegeben. Zugriff auf Benachrichtigungen, die der App nicht gewährt wurden. Ich benutze Android L
Jame

2
Dies erfordert einen zusätzlichen Schritt zum expliziten Erteilen der Berechtigung durch den Benutzer, irgendwo im Einstellungsmenü, abhängig vom System, sowie das Akzeptieren der Berechtigung im Manifest.
Headuck

Die Meldung scheint in einem engen Fall zu funktionieren: Galaxy A3 2016 mit Marshmallow. Ich werde dies in einer Gruppe von A3-Geräten testen, die aufgrund einer FATAL EXCEPTION: java.lang.SecurityException nicht mit der Input Keyevent-Methode arbeiten: Für das Injizieren in eine andere Anwendung ist die Berechtigung INJECT_EVENTS erforderlich. Beleidigende Geräte machen etwa 2% meiner Benutzer aus, und ich repliziere ihre Ausnahme nicht, versuche jedoch mit dieser Methode, festzustellen, ob sie den Anruf entgegennehmen können. Zum Glück fordert meine App bereits eine explizite Benachrichtigung an. Zugang für andere Zwecke.
LeRobot

Nach umfangreichen Tests freue ich mich, Ihnen mitteilen zu können, dass A3 2016-Geräte, bei denen die Ausführung "input keyevent" fehlgeschlagen ist, mit der MediaController-Methode # dispatchMediaButtonEvent (<hook KeryEvent>)) funktionieren konnten. Dies funktioniert natürlich nur, nachdem der Benutzer den expliziten Benachrichtigungszugriff zugelassen hat. Daher müssen Sie einen Bildschirm hinzufügen, der zu den Android-Einstellungen führt, und der Benutzer muss im Grunde zusätzliche Maßnahmen ergreifen, wie in der Antwort beschrieben. In meiner App haben wir zusätzliche Schritte unternommen, um dies immer wieder zu fragen, ob der Benutzer zu diesem Bildschirm wechselt, aber keine Benachrichtigung hinzufügt. Zugang
leRobot

Dies funktioniert auf Android Nougat. Die Lösung von @notz funktioniert ansonsten hervorragend, beschwert sich jedoch über "Nur das System kann Medienschlüsselereignisse an die Sitzung mit globaler Priorität senden" auf Android 7.
Peng Bai

9

Versuchen Sie input keyevent 79die Konstante für KeyEvent.KEYCODE_HEADSETHOOK , um die Antwort von @Muzikant ein wenig zu erläutern und sie ein wenig zu ändern, damit sie auf meinem Gerät ein bisschen sauberer funktioniert . Sehr grob:

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

Verzeihen Sie die ziemlich schlechten Codierungskonventionen, ich bin mit Runtime.exec () -Aufrufen nicht allzu vertraut. Beachten Sie, dass mein Gerät weder gerootet ist noch Root-Rechte anfordert.

Das Problem bei diesem Ansatz ist, dass er nur unter bestimmten Bedingungen funktioniert (für mich). Das heißt, wenn ich den obigen Thread über eine Menüoption ausführe, die der Benutzer auswählt, während ein Anruf klingelt, wird der Anruf einwandfrei beantwortet. Wenn ich es von einem Empfänger aus starte, der den Status eingehender Anrufe überwacht, wird es vollständig ignoriert.

Auf meinem Nexus 5 eignet es sich daher gut für benutzergesteuerte Antworten und sollte dem Zweck eines benutzerdefinierten Anrufbildschirms entsprechen. Es funktioniert einfach nicht für automatisierte Anwendungen vom Typ Anrufsteuerung.

Bemerkenswert sind auch alle möglichen Einschränkungen, einschließlich der Tatsache, dass auch dies wahrscheinlich in ein oder zwei Updates nicht mehr funktioniert.


input keyevent 79funktioniert gut auf Sony Xperia 5.0. Funktioniert beim Anrufen von einer Aktivität oder von einem Rundfunkempfänger.
Nicolas

0

über die adb-Befehle So nehmen Sie einen Anruf von adb an

Denken Sie daran, dass Android Linux mit einer massiven JVM im Frontend ist. Sie können eine Befehlszeilen-App herunterladen und das Telefon rooten. Jetzt verfügen Sie über einen normalen Linux-Computer und eine Befehlszeile, die alle normalen Aufgaben ausführen. Führen Sie Skripte aus, Sie können sogar ssh darauf (OpenVPN-Trick)


0

Danke @notz die Antwort von arbeitet für mich am Lolillop. Damit dieser Code mit dem alten Android SDK funktioniert, können Sie diesen Code ausführen:

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

So schalten Sie die Freisprecheinrichtung ein, nachdem Sie Anrufe automatisch beantwortet haben.

Ich habe mein Problem oben mit setSpeakerphoneOn gelöst. Ich denke, es lohnt sich, hier etwas zu posten, da der Anwendungsfall für die automatische Beantwortung eines Telefonanrufs häufig auch eine nützliche Freisprecheinrichtung erfordert. Nochmals vielen Dank an alle in diesem Thread, was für ein großartiger Job.

Dies funktioniert für mich unter Android 5.1.1 auf meinem Nexus 4 ohne ROOT. ;)

Erlaubnis erforderlich:

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

Java Code:

// 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
Interessant. Ich versuche tatsächlich, einen Anruf anzunehmen und den Lautsprecher gemeinsam einzuschalten, so dass dieser Ansatz beide Probleme zu lösen scheint :). Ich habe ähnliche Fragen wie einige der Kommentare in anderen Antworten: Wohin geht dieser Code?
fangmobile.com

-1

Führen Sie den folgenden Befehl als root aus:

input keyevent 5

Weitere Details zur Simulation von Keyevents finden Sie hier .

Mit dieser von mir erstellten Basisklasse können Sie Befehle als Root in Ihrer App ausführen.


1
Beim Testen mit einem normalen Benutzerprofil wurde die Benutzeroberfläche für Anrufe aufgerufen, und ich wurde aufgefordert, nach links / rechts zu wischen, um abzulehnen / zu antworten oder eine schnelle Aktion / Antwort zu verwenden. Wenn das OP einen benutzerdefinierten Bildschirm für eingehende Anrufe erstellt , ist dies nicht wirklich hilfreich, es sei denn, es verhält sich unter root anders. Ich bezweifle, dass es sich für einen normalen Benutzer nicht gut verhält. Der Anruf würde wahrscheinlich einfach fehlschlagen und nicht eine andere Aktion auslösen.
Valter Jansons

-2

Testen Sie dies: Fügen Sie zuerst die Berechtigungen hinzu und verwenden Sie dann killCall (), um aufzulegen. Verwenden Sie answerCall (), um den Anruf anzunehmen

<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

Zu Ihrer Information, wenn Sie daran interessiert sind, wie Sie einen laufenden Anruf auf Android O, Valter's, beenden können Method 1: TelephonyManager.answerRingingCall() funktioniert wenn Sie die von Ihnen Methode ändern endCall.

Es erfordert nur die android.permission.CALL_PHONE Erlaubnis .

Hier ist der Code:

// 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);
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.