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

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



답변

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는 제한이 없거나 단순히 문서가 없습니다. 주의해서 진행해야합니다.


답변

완전히 작동하는 솔루션은 @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);


답변

다음은 나를 위해 일한 대체 접근 방식입니다. 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 수준을 사용하지 않는 한 더 이상이 해킹이 필요하지 않을 수 있습니다.


답변

@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에서는 사용자 중심 응답에 적합하며 사용자 지정 통화 화면의 목적에 적합해야합니다. 모든 종류의 자동 통화 제어 유형 응용 프로그램에서는 작동하지 않습니다.

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


답변

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

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


답변

감사합니다 @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();
    }
  }
}


답변

자동 응답 후 스피커폰을 켜는 방법.

위의 문제를 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
}