Android 5.0 (Lollipop)에서 프로그래밍 방식으로 수신 전화에 응답하려면 어떻게해야합니까?


87

들어오는 호출에 대한 사용자 지정 화면을 만들려고 할 때 프로그래밍 방식으로 들어오는 호출에 응답하려고합니다. 다음 코드를 사용하고 있지만 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");

오 이런, 왜 이걸로 뛰어 들어, 그냥 슬라이드 맨! 나에게 쉽게 보인다 \ m /
nobalG

Android 사용자를위한 맞춤 수신 전화 화면을 만들고 있습니다.
maveroid

2
누군가? 이것도 관심이 있습니다! 많은 것을 시도했지만 작동하지 않았습니다 : /
Arthur

1
@nobalG 그는 프로그래밍 말하고있다
dsharew

1
@maveroid, Android 5.0에 대한 해결 방법을 생각해 냈습니까?
arthursfreire 2015 년

답변:


155

Android 8.0 Oreo로 업데이트

이 질문은 원래 Android L 지원에 대한 질문 이었지만 사람들은 여전히이 질문과 답변에 답하는 것처럼 보이므로 Android 8.0 Oreo에 도입 된 개선 사항을 설명 할 가치가 있습니다. 이전 버전과 호환되는 방법은 아래에 계속 설명되어 있습니다.

무엇이 바뀌 었습니까?

을 시작으로 안드로이드 8.0 오레오전화 권한 그룹은 또한 포함 ANSWER_PHONE_CALLS의 권한을 . 권한의 이름에서 알 수 있듯이이 권한을 유지하면 리플렉션을 사용하거나 사용자를 시뮬레이션하지 않고도 적절한 API 호출을 통해 앱이 프로그래밍 방식으로 수신 호출을 수락 할 수 있습니다.

이 변경 사항을 어떻게 활용합니까?

이전 Android 버전을 지원하는 경우 런타임시 시스템 버전을 확인 해야 이전 Android 버전에 대한 지원을 유지하면서이 새로운 API 호출을 캡슐화 할 수 있습니다. 최신 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 )에 의한 방법에 힘내 트리가 구조화되었습니다. 즉, Lollipop으로 만 장치를 지원하지 않는 한, 현재로서는 시장 점유율이 적다는 사실을 고려할 때 잘못된 결정일 수 있지만이 경로를 따라가는 경우에도 대체 방법을 제공해야합니다.

이것을 어떻게 사용할까요?

문제의 메서드는 SDK 응용 프로그램 사용에서 숨겨져 있으므로 런타임 중에 메서드를 동적으로 검사하고 사용 하려면 리플렉션 을 사용해야합니다. 반성에 익숙하지 않은 경우 반성 이란 무엇이며 왜 유용합니까?를 빨리 읽을 수 있습니다 . . 관심이있는 경우 Trail : The Reflection API 에서 세부 사항을 자세히 알아볼 수도 있습니다 .

코드에서는 어떻게 보입니까?

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

이것은 사실이 되기에는 너무 좋습니다!

사실 약간의 문제가 하나 있습니다. 이 메소드는 완전히 작동해야하지만 보안 관리자는 호출자가 android.permission.MODIFY_PHONE_STATE 를 보유하기를 원합니다 . 이 권한은 제 3자가 시스템을 건드리지 않을 것으로 예상되기 때문에 시스템의 부분적으로 문서화 된 기능의 영역에 있습니다 (문서에서 볼 수 있듯이). 를 추가해 볼 수는 <uses-permission>있지만이 권한의 보호 수준이 signature | system 이기 때문에 좋지 않습니다 ( 5.0.0_r1의 core / AndroidManifest 1201 줄 참조 ).

당신이 읽을 수있는 문제 34785을 : 업데이트는 안드로이드 : protectionLevel 문서 2012 년에 다시 만들어진 우리는 "파이프 구문"특정에 대한 세부 사항을 누락 있는지,하지만 주위 실험에서, 모든 의미 그것이 역할을한다 표시 'AND' 권한이 부여 되려면 지정된 플래그가 충족되어야합니다. 이러한 가정하에 작업하면 애플리케이션이 있어야합니다.

  1. 시스템 애플리케이션으로 설치됩니다.

    이는 문제가 없으며 아직 패키지화되지 않은 맞춤 ROM에 Google 앱을 루팅하거나 설치할 때와 같이 복구시 ZIP을 사용하여 설치하도록 사용자에게 요청하여 수행 할 수 있습니다.

  2. 프레임 워크 /베이스 (일명 ROM)와 동일한 서명으로 서명됩니다.

    여기에서 문제가 발생합니다. 이렇게하려면 프레임 워크 /베이스에 서명하는 데 사용되는 키를 손에 넣어야합니다. Nexus 공장 이미지에 대한 Google의 키에 액세스해야 할뿐만 아니라 다른 모든 OEM 및 ROM 개발자의 키에도 액세스해야합니다. 이것은 그럴듯 해 보이지 않으므로 사용자 정의 ROM을 만들고 사용자에게 전환하도록 요청하거나 (어려울 수 있음) 권한 보호 수준을 우회 할 수있는 익스플로잇을 찾아 시스템 키로 애플리케이션에 서명 할 수 있습니다. (어려울 수도 있습니다).

또한이 동작은 문제 34792 와 관련된 것으로 보입니다. Android Jelly Bean / 4.1 : android.permission.READ_LOGS는 더 이상 문서화되지 않은 개발 플래그와 함께 동일한 보호 수준을 활용하는 작동하지 않습니다 .

TelephonyManager로 작업하는 것은 좋게 들리지만 실제로는 쉽지 않은 적절한 권한을 얻지 않으면 작동하지 않습니다.

다른 방법으로 TelephonyManager를 사용하는 것은 어떻습니까?

안타깝게도 멋진 도구를 사용 하려면 android.permission.MODIFY_PHONE_STATE 를 보유해야하므로 해당 메소드에 액세스하는 데 어려움을 겪게됩니다.


방법 2 : 서비스 콜 서비스 코드

기기에서 실행중인 빌드가 지정된 코드로 작동하는지 테스트 할 수있는 경우.

TelephonyManager와 상호 작용할 수 없으면 service실행 파일을 통해 서비스와 상호 작용할 수도 있습니다 .

어떻게 작동합니까?

매우 간단하지만이 경로에 대한 문서는 다른 것보다 훨씬 적습니다. 우리는 실행 파일이 서비스 이름과 코드라는 두 가지 인수를받습니다.

  • 서비스 이름 우리가 사용하려는입니다 전화 .

    이것은를 실행하여 볼 수 있습니다 service list.

  • 코드 우리가 사용하고자는 것처럼 보인다 (6) 하지만 지금은 것 같다 5 .

    이를 기반으로 된 것 같습니다 IBinder.FIRST_CALL_TRANSACTION 지금은 많은 버전 + 5 (에서 1.5_r44.4.4_r1 )하지만 현지 테스트하는 동안 코드 5 걸려 오는 전화에 응답했다. Lollipo는 전체적으로 대규모 업데이트이므로 여기에서도 내부가 변경된 것은 이해할 수 있습니다.

이 결과는 service call phone 5.

이를 프로그래밍 방식으로 어떻게 활용합니까?

자바

다음 코드는 개념 증명으로 작동하도록 만들어진 대략적인 구현입니다. 당신이 실제로 가서이 방법을 사용하려면, 당신은 아마 체크 아웃 할 문제가없는 스와 사용에 대한 가이드 라인을 가능성이 더 완벽하게 개발로 전환 libsuperuser 에 의해 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();
}

명백한

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

정말 루트 액세스가 필요합니까?

슬프게도 그렇게 보인다. Runtime.exec 를 사용해 볼 수 는 있지만 그 경로로 운이 좋지 않았습니다.

이것은 얼마나 안정적인가요?

물어봐서 기뻐요. 문서화되지 않았기 때문에 위의 코드 차이에서 알 수 있듯이 다양한 버전으로 나눌 수 있습니다. 서비스 이름은 다양한 빌드에서 전화로 유지되어야 하지만, 우리가 아는 한 코드 값은 동일한 버전의 여러 빌드 (예 : OEM 스킨에 의한 내부 수정)에서 변경 될 수 있으며 사용 된 방법을 깨뜨릴 수 있습니다. 따라서 테스트가 Nexus 4 (mako / occam)에서 수행되었음을 언급 할 가치가 있습니다. 개인적으로이 방법을 사용하지 말라고 조언하지만 더 안정적인 방법을 찾을 수 없기 때문에 이것이 최선이라고 생각합니다.


원래 방법 : 헤드셋 키 코드 의도

정착해야 할 때를 위해.

다음 섹션은 Riley C 의이 답변 에 크게 영향을 받았습니다 .

원래 질문에 게시 된 시뮬레이션 된 헤드셋 의도 방법은 예상대로 방송되는 것처럼 보이지만 전화 응답 목표를 달성하지 못하는 것 같습니다. 이러한 인 텐트를 처리해야하는 코드가있는 것처럼 보이지만 단순히 신경 쓰지 않고 있으므로이 방법에 대한 새로운 대응책이 있어야합니다. 로그에도 관심있는 내용이 표시되지 않으며 저는 개인적으로 Android 소스를 파헤치는 것이 Google이 어쨌든 사용 된 방법을 쉽게 깨뜨리는 약간의 변경을 도입 할 가능성이 있기 때문에 가치가 있다고 생각하지 않습니다.

지금 당장 할 수있는 일이 있습니까?

입력 실행 파일을 사용하여 동작을 일관되게 재현 할 수 있습니다. 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.0 Oreo 이상을위한 멋진 공용 API가 있습니다.

Android 8.0 Oreo 이전에는 공개 API가 없습니다. 내부 API는 제한이 없거나 단순히 문서가 없습니다. 주의해서 진행해야합니다.


헤드셋 키 코드 의도와 관련하여 여기 에서 Google 소스를 확인한 적이 있습니까? 재미있는 점은 이러한 의도로 통화를 쉽게 거부 할 수 있지만 (길게 누르기 만하면됩니다) 응답 할 수있는 것은 없다는 것입니다. 나는 아직 명시적인 허가 확인이나 다른 잠재적 차단을 찾지 못했고 두 번째 눈이 무언가를 발견 할 수 있기를 바라고 있습니다.
Riley C

따라서 지연이 조금 바빴습니다-이것을 알아내는 데 시간을 할애하려고 할 것입니다. 간략히 살펴보면 CallsManager가 HeadsetMediaButton을 구성하는 것 같습니다. 세션 콜백 은 MediaSessionManager의 콜백시 handleHeadsetHook (KeyEvent) 호출을 처리해야합니다. 모든 코드가 일치하는 것 같지만 누군가가 KeyEvent.ACTION_DOWN 의도를 제거하여 테스트 할 수 있을까요? (즉, KeyEvent.ACTION_UP을 한 번만 실행합니다.)
Valter Jansons 2014

실제로 HeadsetMediaButton은 MediaSession과 함께 작동하며 MediaSessionManager와 직접 상호 작용하지 않습니다 ...
Valter Jansons

1
Original Method가 작동하지 않는 상황 (예 : Lollipop에 문제가 있음)을 발견 한 분들에게 좋은 소식과 나쁜 소식이 있습니다. FULL_WAKE_LOCK. PARTIAL_WAKE_LOCK에서는 작동하지 않습니다. 그 이유에 대한 문서는 전혀 없습니다. 실험 코드를 더 광범위하게 테스트 할 때 향후 답변에서 자세히 설명하겠습니다. 물론 나쁜 소식은 FULL_WAKE_LOCK이 더 이상 사용되지 않으므로 Google이 API에 유지하는 한 지속되는 수정입니다.
leRobot 2015-04-09

1
원래 답변의 캐치는 많은 경우 호출되지 않습니다. 먼저 exec를 호출 한 다음 어쨌든 바로 버튼을 아래로 호출하는 것이 더 낫다는 것을 알았습니다.
Warpzit

36

완전히 작동하는 솔루션은 @Valter Strods 코드를 기반으로합니다.

작동하게하려면 코드가 실행되는 잠금 화면에 (보이지 않는) 활동을 표시해야합니다.

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는 "Finally call the magic"아래에 코드를 추가합니다. Android 6.0에서 작동
합니까

나는 broadcastHeadsetConnected (boolean connected)가 삼성 A3 2016 기기의 문제를 해결했다고 여기에 왔습니다. 그것 없이는 매우 유사한 방법 (별도의 투명한 활동 및 호출 및 기타 작업에 대한 스레드 사용)이 약 20 개의 테스트 된 장치에서 완전히 작동 했으며이 A3가 등장 하여이 질문을 다시 확인하여 새로운 답변을 얻었습니다. 내 코드와 비교 한 결과 큰 차이였습니다!
leRobot

1
전화를 거절하려면 어떻게해야합니까? 이것을 보여주기 위해 답변을 업데이트 할 수 있습니까?
Amanni

@leRobot이 답변은 방송 할 HTC 기기인지 확인합니다 .HeadsetConnected, 삼성 A3 2016 기기인지 어떻게 확인할 수 있나요? 그건 그렇고 이것은 정말 좋은 대답입니다. 내 앱은 화면이 잠겨 있어도 전화를받을 수 있습니다.
eepty

@eepty 빌드 데이터에 대한 공식 장치 참조를 사용할 수 있습니다. ( support.google.com/googleplay/answer/1727131?hl=ko ). A3 2016에는 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 수준을 사용하지 않는 한 더 이상이 해킹이 필요하지 않을 수 있습니다.


그것은 나를 위해 작동하지 않았습니다. 권한 오류를 반환했습니다. 알림에 대한 액세스 권한이 앱에 부여되지 않았습니다. 나는 안드로이드 L를 사용하고 있습니다
Jame

2
이를 위해서는 매니페스트에서 권한을 수락하는 것 외에도 시스템에 따라 설정 메뉴의 어딘가에서 사용자가 명시 적으로 권한을 부여하는 추가 단계가 필요합니다.
headuck

이것을보고하는 것은 좁은 경우에 작동하는 것 같습니다 : Marshmallow가있는 Galaxy A3 2016. FATAL EXCEPTION : java.lang.SecurityException : Injecting to another application requires INJECT_EVENTS permission. 문제가되는 장치는 내 사용자 기반의 약 2 %이며 예외를 복제하지는 않지만이 방법을 시도하여 전화를받을 수 있는지 확인합니다. 다행히 내 앱은 이미 명시적인 알림을 요청하고 있습니다. 다른 목적을위한 액세스.
leRobot

광범위한 테스트를 마친 후 exec "input keyevent"로 실패한 A3 2016 장치가 MediaController # dispatchMediaButtonEvent (<hook KeryEvent>)) 메서드와 함께 작동했다고보고하게되어 기쁩니다. 이것은 분명히 사용자가 명시적인 알림 액세스를 허용 한 후에 만 ​​작동하므로이를 위해 Android 설정으로 안내하는 화면을 추가해야하며 기본적으로 사용자가 이에 대한 추가 조치를 취해야합니다. 내 앱에서는 사용자가 해당 화면으로 이동하지만 알림을 추가하지 않는지 계속 묻는 추가 단계를 수행했습니다. 액세스
leRobot

이것은 Android Nougat에서 작동합니다. @notz의 솔루션은 그렇지 않으면 훌륭하게 작동하지만 Android 7에서는 "시스템 만 미디어 키 이벤트를 글로벌 우선 순위 세션에 전달할 수 있습니다."라고 불평합니다.
Peng Bai

9

@Muzikant의 답변에 대해 약간 자세히 설명하고 내 장치에서 약간 더 깔끔하게 작동하도록 약간 수정 input keyevent 79하려면 KeyEvent.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 () 호출에 너무 정통하지 않습니다. 내 장치는 루팅되지 않았으며 루트 권한을 요청하지도 않습니다.

이 접근 방식의 문제점은 특정 조건에서만 작동한다는 것입니다. 즉, 전화가 울리는 동안 사용자가 선택한 메뉴 옵션에서 위의 스레드를 실행하면 전화가 제대로 응답됩니다. 수신 전화 상태를 모니터링하는 수신기에서 실행하면 완전히 무시됩니다.

따라서 내 Nexus 5에서는 사용자 중심 응답에 적합하며 사용자 지정 통화 화면의 목적에 적합해야합니다. 모든 종류의 자동 통화 제어 유형 응용 프로그램에서는 작동하지 않습니다.

또한이 역시 업데이트에서 작동이 중지 될 수 있다는 점을 포함하여 가능한 모든주의 사항이 있습니다.


input keyevent 79Sony Xperia 5.0에서 잘 작동합니다. 활동 또는 브로드 캐스트 수신기에서 호출 할 때 작동합니다.
nicolas

0

adb 명령을 통해 adb 로 전화를받는 방법

Android는 프런트 엔드에 대규모 JVM이있는 Linux입니다. 명령 줄 앱을 다운로드하고 전화를 루팅 할 수 있으며 이제 일반 Linux 컴퓨터와 모든 정상적인 작업을 수행하는 명령 줄이 있습니다. 스크립트 실행, ssh도 가능합니다 (OpenVPN 트릭).


0

감사합니다 @notz의 대답은 Lolillop에서 나를 위해 일하고 있습니다. 이 코드가 이전 Android SDK에서 계속 작동하도록하려면 다음 코드를 수행 할 수 있습니다.

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가없는 Nexus 4의 Android 5.1.1에서 나를 위해 작동합니다. ;)

필요한 권한 :

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

자바 코드 :

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

-1

다음 명령을 루트로 실행하십시오.

input keyevent 5

키 이벤트 시뮬레이션에 대한 자세한 내용은 여기를 참조 하십시오 .

내가 만든이 기본 클래스를 사용 하여 앱에서 루트로 명령을 실행할 수 있습니다.


1
일반 사용자 프로필로 테스트하는 동안 통화 중 UI가 표시되어 거절 / 응답 또는 빠른 작업 / 응답을 사용하려면 왼쪽 / 오른쪽으로 스 와이프하도록 요청했습니다. 영업 이익이 경우 사용자 지정 수신 전화 화면을 만들기 는 통화가 아마 단순히 실패 것이다 나는 그것이 일반 사용자를 위해 잘 작동하지 않는 것처럼 의심되는 다른 루트에서 작동하지 않는 한,이 정말 어떤 도움이되지 않습니다 하지 다른 행동을 유발합니다.
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()있다면 호출하는 메서드를 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.