Android 5.0(Lollipop)で着信にプログラムで応答するにはどうすればよいですか?


87

着信のカスタム画面を作成しようとしているので、プログラムで着信に応答しようとしています。次のコードを使用していますが、Android5.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");

ああ男、なぜこれに遭遇するのか、ただ男をスライドさせてください!私には簡単に思えます\ m /
nobalG 2014年

Androidユーザー向けのカスタム着信画面を作成しています。
マーベロイド2014年

2
誰でも?これにも興味があります!多くのことを試みましたが、うまくいきませんでした:/
アーサー

1
@nobalG彼はプログラムで言っています
dsharew 2014年

1
@ maveroid、Android 5.0の回避策を思いつきましたか?
arthursfreire 2015年

回答:


155

Android 8.0Oreoで更新

質問は元々AndroidLのサポートを求められていましたが、人々はまだこの質問と回答に答えているようです。そのため、Android 8.0Oreoで導入された改善点について説明する価値があります。下位互換性のある方法については、以下で説明します。

何が変わったの?

以降ではアンドロイド8.0オレオPHONEの許可グループはまた、含まれていANSWER_PHONE_CALLSの許可を。権限の名前が示すように、権限を保持すると、アプリは、リフレクションを使用したりユーザーをシミュレートしたりしてシステムをハッキングすることなく、適切なAPI呼び出しを介して着信呼び出しをプログラムで受け入れることができます。

この変更をどのように利用しますか?

あなたは必要があり、実行時にシステムのバージョンを確認し、これらの古いAndroidのバージョンのサポートを維持しながら、あなたは、この新しいAPI呼び出しをカプセル化できるように、古いAndroidのバージョンをサポートしている場合。あなたは従うべきで、実行時に許可を要求する、新しいAndroidのバージョンに標準装備されて、実行時にその新しい許可を得ること。

権限を取得した後、アプリはTelecomManagerのacceptRingingCallメソッドを呼び出すだけです。基本的な呼び出しは次のようになります。

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

方法1:TelephonyManager.answerRingingCall()

デバイスを無制限に制御できる場合。

これは何ですか?

隠された内部メソッドであるTelephonyManager.answerRingingCall()があります。これは、インターウェブで議論されており、最初は有望と思われるITelephony.answerRingingCall()のブリッジとして機能します。それはないに利用できる4.4.2_r1それが唯一で導入されたとしてコミット83da75dをアンドロイド4.4キットカット(ため4.4.3_r1上のライン1537)以降のコミットで「再導入」f1e1e77ロリポップ用(5.0.0_r1上に線3138を原因どのように) Gitツリーが構造化されました。つまり、Lollipopを搭載したデバイスのみをサポートする場合を除きます。これは、現時点でのわずかな市場シェアに基づくとおそらく悪い決定ですが、このルートを進む場合は、フォールバック方法を提供する必要があります。

これをどのように使用しますか?

問題のメソッドはSDKアプリケーションの使用から隠されているため、リフレクションを使用して、実行時にメソッドを動的に調べて使用する必要があります。リフレクションに慣れていない場合は、リフレクションとは何ですか。なぜそれが役立つのでしょうか。。興味がある場合は、Trail:The ReflectionAPIで詳細を掘り下げることもできます。

そして、それはコードでどのように見えますか?

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

これはあまりにも良すぎて真実ではありません!

実際、ちょっとした問題が1つあります。このメソッドは完全に機能するはずですが、セキュリティマネージャーは、呼び出し元がandroid.permission.MODIFY_PHONE_STATEを保持することを望んでいます。この許可は、サードパーティがそれに触れることを期待されていないため、システムの部分的に文書化された機能の領域にあります(システムのドキュメントからわかるように)。を追加し<uses-permission>てみることができますが、この権限の保護レベルは署名|システムであるため、うまくいきません(5.0.0_r1のcore / AndroidManifestの1201行目を参照)。

あなたは読むことができる問題34785を:のProtectionLevelドキュメント:アップデートは、Android、我々は特定の「パイプの構文」の詳細が欠けていることを確認するために2012年に戻ってを作成しましたが、周りの実験から、「AND」のすべてを意味するものとして機能しなければならない表示されます許可を与えるには、指定されたフラグを満たす必要があります。その仮定の下で作業すると、アプリケーションが必要になることを意味します。

  1. システムアプリケーションとしてインストールされます。

    これは問題ないはずであり、まだパッケージ化されていないカスタムROMにGoogleアプリをルート化またはインストールする場合など、リカバリでZIPを使用してインストールするようにユーザーに依頼することで実現できます。

  2. フレームワーク/ベース、別名システム、別名ROMと同じ署名で署名されています。

    ここで問題が発生します。これを行うには、フレームワーク/ベースの署名に使用されるキーを手に入れる必要があります。NexusファクトリイメージのGoogleのキーにアクセスする必要があるだけでなく、他のすべてのOEMおよびROM開発者のキーにもアクセスする必要があります。これはもっともらしいとは思えないので、カスタムROMを作成してユーザーに切り替えるように依頼するか(難しい場合があります)、アクセス許可保護レベルをバイパスできるエクスプロイトを見つけることで、システムキーでアプリケーションに署名させることができます。 (これも難しいかもしれません)。

さらに、この動作は、Issue 34792:Android Jelly Bean / 4.1:android.permission.READ_LOGSが機能しなくなり、文書化されていない開発フラグとともに同じ保護レベルを利用するようになりました。

TelephonyManagerの操作は良いように聞こえますが、実際にはそれほど簡単ではない適切な許可を取得しない限り、機能しません。

TelephonyManagerを他の方法で使用するのはどうですか?

残念ながら、クールなツールを使用するにはandroid.permission.MODIFY_PHONE_STATEを保持する必要があるようです。つまり、これらのメソッドにアクセスするのに苦労することになります。


方法2:サービスコールサービスコード

デバイスで実行されているビルドが指定されたコードで動作することをテストできる場合。

TelephonyManagerと対話できなくても、service実行可能ファイルを介してサービスと対話する可能性もあります。

これはどのように作動しますか?

かなり単純ですが、このルートに関するドキュメントは他のルートよりもさらに少なくなっています。実行可能ファイルがサービス名とコードの2つの引数を受け取ることは確かです。

  • サービス名、我々が使用したいです電話

    これは、を実行することで確認できますservice list

  • コード我々はされているように使用したい6が、今のようです5

    現在、多くのバージョン(1.5_r4から4.4.4_r1)でIBinder.FIRST_CALL_TRANSACTION + 5に基づいているようですが、ローカルテスト中に、コード5が着信呼び出しに応答するように機能しました。Lollipoはあらゆる面で大規模な更新で​​あるため、ここでも内部が変更されていることは理解できます。

これは、のコマンドで発生しservice call phone 5ます。

これをプログラムでどのように利用しますか?

Java

次のコードは、概念実証として機能するように作成された大まかな実装です。実際にこの方法を使用したい場合は、問題のないsuの使用に関するガイドラインを確認し、Chainfireによってより完全に開発されたlibsuperuserに切り替えることをお勧めします。

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

マニフェスト

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

これは本当にルートアクセスを必要としますか?

悲しいことに、そう思われます。Runtime.execを使用してみることができますが、そのルートではうまくいきませんでした。

これはどれくらい安定していますか?

よろしくお願いします。文書化されていないため、上記のコードの違いに示されているように、これはさまざまなバージョンにまたがる可能性があります。サービス名はおそらくさまざまなビルド間で電話のままである必要がありますが、私たちが知っている限りでは、コード値は同じバージョンの複数のビルド間で変更される可能性があります(たとえば、OEMのスキンによる内部変更)。したがって、テストはNexus 4(mako / occam)で行われたことに言及する価値があります。個人的にはこの方法を使わないようにアドバイスしたいのですが、もっと安定した方法が見つからないので、これがベストショットだと思います。


元の方法:ヘッドセットのキーコードインテント

あなたが落ち着かなければならない時のために。

次のセクションは、RileyCによるこの回答の影響を強く受けました。

元の質問に投稿されたシミュレートされたヘッドセットインテントメソッドは、予想どおりにブロードキャストされているように見えますが、通話に応答するという目標を達成していないようです。これらの意図を処理するコードが配置されているように見えますが、それらは単に気にされていません。つまり、この方法に対して何らかの新しい対策を講じる必要があります。ログにも関心のあるものは何も表示されていません。Googleがわずかな変更を導入して、とにかく使用されている方法を簡単に破る可能性があるという理由だけで、Androidソースを掘り下げる価値があるとは個人的には思いません。

今できることはありますか?

動作は、入力実行可能ファイルを使用して一貫して再現できます。キーコード引数を取り、KeyEvent.KEYCODE_HEADSETHOOKを渡すだけです。です。このメソッドはルートアクセスを必要としないため、一般の一般的なユースケースに適していますが、このメソッドには小さな欠点があります。ヘッドセットボタンを押すイベントを指定して許可を要求することはできません。つまり、実際のように機能します。ボタンを押すとチェーン全体が泡立ちます。つまり、ボタンを押すタイミングをシミュレートするタイミングに注意する必要があります。たとえば、優先度の高い他の人が処理する準備ができていない場合に、音楽プレーヤーをトリガーして再生を開始することができます。行事。

コード?

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

Android 8.0Oreo以降用の優れたパブリックAPIがあります。

Android 8.0Oreoより前のパブリックAPIはありません。内部APIは立ち入り禁止であるか、単にドキュメントがありません。注意して続行する必要があります。


ヘッドセットキーコードの意図に関しては、あなたは、Googleのソースをチェックアウトした、ここで彼らが作用している停止する理由何らかの理由で?面白いことに、これらのインテントを使用すると、通話を簡単に拒否できますが(長押しをエミュレートするだけです)、応答するものは何もありません。明示的な許可チェックやその他の潜在的なブロックをまだ見つけていません。2番目の目で何かが明らかになることを期待しています。
ライリーC

少し忙しかったので、遅れました-これを理解するのに少し時間を費やそうとします。簡単に見てみると、CallsManagerがHeadsetMediaButtonを構築しているようです。そこでのセッションコールバックは、MediaSessionManagerからのコールバック時にhandleHeadsetHook(KeyEvent)を呼び出すように処理する必要があります。すべてのコードが一致しているようです...しかし、誰かがKeyEvent.ACTION_DOWNインテントを削除してテストできるでしょうか?(つまり、KeyEvent.ACTION_UPを1回だけ起動します。)
Valter Jansons 2014年

実は、HeadsetMediaButtonはMediaSessionで動作し、MediaSessionManagerと相互作用しない直接...
バルターヤンソンス

1
元のメソッドが機能していないように見える状況(たとえば、Lollipopに問題がある)を見つけた人のために、良いニュースと悪いニュースがあります。コードで、ACTION_UPを100%機能させることができました。 FULL_WAKE_LOCK。PARTIAL_WAKE_LOCKでは機能しません。これの理由についてのドキュメントはまったくありません。これについては、今後、実験コードをさらに広範囲にテストするときに詳しく説明します。悪いニュースは、もちろん、FULL_WAKE_LOCKが非推奨になっていることです。したがって、これは、GoogleがAPIに保持している間だけ続く修正です。
leRobot 2015

1
多くの場合、元の回答からのキャッチは呼び出されません。最初にexecを呼び出してから、その直後にボタンを上下に呼び出す方がよいことがわかりました。
Warpzit 2016

36

完全に機能するソリューションは、@ ValterStrodsコードに基づいています。

それを機能させるには、コードが実行されるロック画面に(非表示の)アクティビティを表示する必要があります。

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>

受諾アクティビティを呼び出す

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

スタイル

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

最後に魔法を呼んでください!

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
ここで、mi suppozは、「最後に魔法を呼ぶ」の下にコードを追加します。Android 6.0で動作しますか
Akshay Shah 2016

私はここに来て、broadcastHeadsetConnected(ブール接続)がSamsung A32016デバイスの問題を解決したと言いました。それがなければ、非常によく似た方法(個別の透過的なアクティビティ、呼び出しなどにスレッドを使用)が約20のテスト済みデバイスで完全に機能し、このA3が登場し、この質問に新しい回答がないか再確認する必要がありました。私のコードと比較した後、それは大きな違いでした!
leRobot 2016年

1
どうすれば通話を拒否できますか?これを示すために答えを更新できますか?
アマンニ2016年

@leRobotこの回答は、broadcastHeadsetConnectedのHTCデバイスであるかどうかを確認しますが、Samsung A3 2016デバイスであるかどうかをどのように確認できますか?ちなみに、これは本当に良い答えです。私のアプリは、画面がロックされていても電話に出ることができます。
eepty 2016

@eeptyビルドデータの公式デバイスリファレンスを使用できます。(support.google.com/googleplay/answer/1727131?hl=en)。2016年のA3にはBuild.MODEL.startsWith( "SM-A310")を使用しています。A3 2016がヘッドセット接続のブロードキャストをサポートしていないことを確認できます!。何が実際にRuntime.getRuntime()のexec(これらのデバイスのための最初の...トリガーのように、私の問題は、順序を変更して解決することは、そのデバイスのたびに動作するようだと戻って例外に該当しない。。
leRobot

14

以下は私のために働いた代替アプローチです。MediaController APIを使用して、キーイベントをテレコムサーバーに直接送信します。これには、アプリにBIND_NOTIFICATION_LISTENER_SERVICE権限があり、ユーザーからの通知アクセスが明示的に許可されている必要があります。

@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 上記のコードでは、空のクラスである可能性があります。

import android.service.notification.NotificationListenerService;

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

マニフェストの対応するセクション:

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

イベントのターゲットは明示的であるため、これにより、メディアプレーヤーをトリガーすることによる副作用を回避できるはずです。

注:テレコムサーバーは、呼び出し中のイベントの直後にアクティブにならない場合があります。これが確実に機能するためには、イベントを送信する前に、アプリがMediaSessionManager.OnActiveSessionsChangedListenerを実装して、テレコムサーバーがアクティブになるタイミングを監視すると便利な場合があります。

更新:

ではAndroidのO、シミュレートする一つの必要性ACTION_DOWNの前にACTION_UP、そうでない場合は上記の効果はありません。つまり、次のものが必要です。

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

ただし、Android O以降は公式の応答呼び出しが利用可能であるため(トップアンサーを参照)、Android Oより前の古いコンパイルAPIレベルに固執しない限り、このハックは不要になる可能性があります。


それは私にはうまくいきませんでした。パーミッションエラーを返しました。アプリに許可されていない通知へのアクセス。私は
AndroidL

2
これには、マニフェストで権限を受け入れることに加えて、システムに応じて設定メニューのどこかで、ユーザーによる権限を明示的に付与する追加の手順が必要です。
ヘッドタック2016

これを報告することは、狭いケースで機能するようです:マシュマロを備えたギャラクシーA32016。致命的な例外のためにinputkeyeventメソッドで動作しないA3デバイスのグループでこれをテストします:java.lang.SecurityException:別のアプリケーションに注入するにはINJECT_EVENTS権限が必要です。問題のあるデバイスは私のユーザーベースの約2%であり、例外を複製していませんが、この方法を試して、デバイスがなんとか電話に出られるかどうかを確認します。幸い、私のアプリはすでに明示的な通知を要求しています。他の目的のためのアクセス。
leRobot 2017年

徹底的なテストの後、exec "inputkeyevent"で失敗していたA32016デバイスがMediaController#dispatchMediaButtonEvent(<hook KeryEvent>))メソッドで動作するように管理されたことを報告できてうれしいです。これは明らかに、ユーザーが明示的な通知アクセスを許可した後にのみ機能するため、Android設定に誘導する画面を追加する必要があります。基本的に、回答で詳しく説明されているように、ユーザーはこれに対して追加のアクションを実行する必要があります。私のアプリでは、ユーザーがその画面に移動したが通知を追加しないかどうかを尋ね続けるために、追加の手順を実行しました。アクセス
leRobot 2017年

これはAndroidNougatで動作します。@notzのソリューションは、そうでない素晴らしい作品が、Androidの7の「唯一のシステムはグローバル優先セッションにメディアキーイベントをディスパッチすることができます」不平を言う
鵬白

9

@Muzikantによる回答について少し詳しく説明し、デバイスで少しクリーンに動作するように少し変更するにはinput keyevent 79KeyEvent.KEYCODE_HEADSETHOOKの定数を試してください。非常に大まかに:

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

かなり悪いコーディング規約を許してください、私はRuntime.exec()呼び出しにあまり精通していません。私のデバイスはroot化されておらず、root権限を要求していないことに注意してください。

このアプローチの問題点は、特定の条件下でのみ機能することです(私にとって)。つまり、通話の呼び出し中にユーザーが選択したメニューオプションから上記のスレッドを実行すると、通話は正常に応答します。着信状態を監視する受信機から実行すると、完全に無視されます。

そのため、私のNexus 5では、ユーザー主導の応答に適していて、カスタム通話画面の目的に適しているはずです。自動化された呼制御タイプのアプリケーションでは機能しません。

また、これもおそらく1、2回のアップデートで機能しなくなることを含め、考えられるすべての警告に注意してください。


input keyevent 79Sony Xperia5.0で正常に動作します。アクティビティまたはブロードキャストレシーバーから呼び出すときに機能します。
ニコラ2016年

0

adbコマンドを介してadbで 電話を受ける方法

AndroidはLinuxであり、フロントエンドに大規模なJVMがあることに注意してください。コマンドラインアプリをダウンロードして電話をルート化すると、通常のLinuxコンピューターとコマンドラインができて、通常のことをすべて実行できます。スクリプトを実行し、それにsshすることもできます(OpenVPNトリック)


0

ありがとう@notzの答えはLolillopで私のために働いています。このコードを古いAndroidSDKで機能させ続けるために、次のコードを実行できます。

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

自動的に電話に出た後にスピーカーフォンをオンにする方法。

上記の問題はsetSpeakerphoneOnで解決しました。電話に自動応答するユースケースでは、スピーカーフォンも役立つことが多いため、ここに投稿する価値があると思います。このスレッドの皆さんにもう一度感謝します。なんて素晴らしい仕事でしょう。

これは、ROOTなしのNexus4のAndroid5.1.1で機能します。;)

必要な許可:

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

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
面白い。私は実際に電話に出てスピーカーを一緒にオンにしようとしているので、このアプローチは両方を解決するようです:)。しかし、他の回答のコメントのいくつかと同様の質問があります:このコードはどこに行きますか?
fangmobile 2016年

-1

rootとして次のコマンドを実行します。

input keyevent 5

キーイベントのシミュレーションの詳細については、こちらをご覧ください

私が作成したこの基本クラスを使用して、アプリからrootとしてコマンドを実行できます。


1
通常のユーザープロファイルでテストしているときに、通話中のUIが表示され、左/右にスワイプして拒否/応答するか、クイックアクション/応答を使用するように求められました。OPがされている場合は、カスタム着信画面を作成し、それはルートの下に異なった動作をしない限り、これは私はそれが通常のユーザーのためにうまく動作しませんでしたかのように、コールはおそらく単に失敗しないだろうこれは、実際に任意のヘルプのではないではありません別のアクションをトリガーします。
Valter Jansons 2014年

-2

これをテストします。最初に権限を追加してから、killCall()を使用して電話を切り、answerCall()を使用して電話に応答します。

<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

参考までに、Android Oで進行中の通話を終了する方法に興味があるMethod 1: TelephonyManager.answerRingingCall()場合は、呼び出すメソッドをに変更するとValterが機能しますendCall

android.permission.CALL_PHONE許可が必要なだけです。

コードは次のとおりです。

// 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);
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.