Как программировать входящие вызовы в 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");

Ответ 1

Обновление для Android O

Несмотря на то, что вопрос был первоначально задан для поддержки Android L, люди все еще, похоже, сталкиваются с этим вопросом и ответом, поэтому стоит упомянуть последние улучшения в Android O. Способы обратной совместимости все еще описаны ниже.

Что изменилось?

Начиная с Android O, группа разрешений 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(), который обсуждался на сайтах interwebs и кажется многообещающим с самого начала. Это не доступно 4.4.2_r1, поскольку оно было введено только в commit 83da75d для Android 4.4 KitKat (строка 1537 на 4.4.3_r1), а затем "reactroduced" in commit f1e1e77 для Lollipop (строка 3138 на 5.0.0_r1) из-за того, как было структурировано дерево Git. Это означает, что, если вы не поддерживаете только устройства с Lollipop, что, вероятно, является плохим решением, основанным на крошечной доле рынка на данный момент, вам все равно необходимо предоставить резервные методы, если пойдет по этому маршруту.

Как мы будем использовать это?

Поскольку этот метод скрыт от использования приложений SDK, вам необходимо использовать reflection для динамического изучения и использования метода во время выполнения. Если вы не знакомы с отражением, вы можете быстро прочитать Что такое отражение и почему оно полезно?. Вы также можете углубиться в спецификацию Trail: 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. Это разрешение относится только к частично документированным функциям системы, так как третьи стороны не должны касаться его (как вы можете видеть из документации для него). Вы можете попробовать добавить <uses-permission>, но это не принесет пользы, потому что уровень защиты для этого разрешения - это подпись | system (см. строку 1201 ядра /AndroidManifest на 5.0.0_r1).

Вы можете прочитать Проблема 34785: обновить документацию по android: protectionLevel, которая была создана в 2012 году, чтобы увидеть, что нам не хватает информации о конкретных "синтаксис трубы", но, экспериментируя вокруг, кажется, что он должен функционировать как "И", то есть все указанные флаги должны быть выполнены для разрешения. Работая в соответствии с этим предположением, это означает, что вы должны иметь свое приложение:

  • Установлено как системное приложение.

    Это должно быть хорошо и может быть достигнуто, попросив пользователей установить с помощью ZIP в восстановление, например, при укоренении или установке приложений Google на пользовательских ПЗУ, у которых их уже нет.

  • Подпись с той же подписью, что и frameworks/base aka the system, а также ROM.

    Здесь возникают проблемы. Для этого вам нужно использовать ключи, используемые для подписания фреймворков/базы. Вам не только нужно будет получить доступ к ключам Google для изображений Nexus factory, но вам также необходимо будет получить доступ ко всем ключам разработчиков других OEM-производителей и ПЗУ. Это не кажется правдоподобным, поэтому вы можете подписаться с системными ключами с помощью пользовательского ПЗУ и попросить своих пользователей переключиться на него (что может быть сложно) или найти эксплойт, с которым можно обойти уровень защиты (что также может быть затруднено).

Кроме того, это поведение, по-видимому, связано с Проблема 34792: Android Желе 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_r4 до 4.4.4_r1), но во время локального тестирования код 5 работал, чтобы ответить входящий вызов. Поскольку Lollipo является массовым обновлением по всему миру, здесь также меняются понятные внутренние элементы.

Это результат команды service call phone 5.

Как мы используем это программно?

Java

Следующий код представляет собой грубую реализацию, выполненную для того, чтобы функционировать как доказательство концепции. Если вы действительно хотите пойти дальше и использовать этот метод, вы, вероятно, захотите проверить рекомендации по беспроблемному использованию su и, возможно, переключиться на более полно 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"/>

Действительно ли это требует доступа root?

К сожалению, это так. Вы можете попробовать использовать Runtime.exec, но я не смог получить удачу с этим маршрутом.

Насколько это стабильно?

Я рад, что ты спросил. Из-за того, что документация не документирована, это может нарушить различные версии, о чем свидетельствует показанная выше разность кода. Имя службы должно, вероятно, оставаться на телефоне в разных сборках, но, насколько нам известно, значение кода может изменяться в нескольких строках одной и той же версии (внутренние модификации, например, с помощью OEM-оболочки), в свою очередь нарушая используемый метод. Поэтому стоит упомянуть, что тестирование проводилось на Nexus 4 (mako/occam). Я лично посоветую вам не использовать этот метод, но поскольку я не могу найти более стабильный метод, я считаю, что это лучший снимок.


Исходный метод: намерение кодировки клавиш гарнитуры

В те времена, когда вам приходится довольствоваться.

На следующий раздел большое влияние оказал этот ответ Riley C.

Имитируемый метод умывания гарнитуры, опубликованный в исходном вопросе, кажется, транслируется так, как и следовало ожидать, но, похоже, он не достигает цели ответа на вызов. Несмотря на то, что, как представляется, существует код, который должен обрабатывать эти намерения, их просто не интересуют, что должно означать, что для этого метода должны быть какие-то новые контрмеры. Журнал также не показывает ничего интересного, и я лично не считаю, что вам нужно копаться через источник Android, потому что это будет полезно только из-за того, что Google может внести небольшое изменение, которое легко нарушит используемый метод.

Есть ли что-нибудь, что мы можем сделать прямо сейчас?

Поведение можно последовательно воспроизводить с помощью исполняемого файла ввода. Он принимает аргумент keycode, для которого мы просто передаем KeyEvent.KEYCODE_HEADSETHOOK. Этот метод даже не требует доступа root, что делает его подходящим для общепринятых случаев для широкой публики, но есть небольшой недостаток в методе - не может быть указано событие нажатия кнопки гарнитуры, требующее разрешения, то есть он работает как реальный нажатие кнопки и пузыри вверх по всей цепочке, что, в свою очередь, означает, что вы должны быть осторожны, когда имитировать нажатие кнопки, поскольку это может привести, например, к запуску музыкального проигрывателя, если никто из более высокого приоритета не готов к обработке событие.

Код?

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 O

  • Нет общедоступного API.
  • Внутренние API-интерфейсы отключены или просто без документации.
  • Вы должны действовать осторожно.

Ответ 2

Полностью работающее решение основано на коде @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);

Ответ 3

Чтобы разработать крошечный бит в ответе @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(). Обратите внимание, что мое устройство не внедрено, и я не запрашиваю привилегии root.

Проблема с этим подходом заключается в том, что он работает только при определенных условиях (для меня). То есть, если я запустил вышеуказанный поток из опции меню, которую пользователь выбирает во время звонка, звонок отвечает просто отлично. Если я запустил его из приемника, который контролирует статус входящего вызова, он полностью игнорируется.

Итак, на моем Nexus 5 он хорошо работает для пользовательских ответов и должен соответствовать специальной настройке экрана вызова. Он просто не будет работать для каких-либо автоматизированных приложений типа управления вызовами.

Также следует отметить все возможные оговорки, в том числе, что это тоже, вероятно, перестанет работать в обновлении или два.

Ответ 4

Ниже приведен альтернативный подход, который работал у меня. Он отправляет ключевое событие на телекоммуникационный сервер напрямую с помощью API MediaController. Для этого требуется, чтобы приложение BIND_NOTIFICATION_LISTENER_SERVICE разрешало и предоставляло явное предоставление доступа Notification от пользователя:

@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 (см. главный ответ), больше не может понадобиться этот взлом, если только у вас нет старого уровня API компиляции до Android O.

Ответ 5

Спасибо @notz, ответ на меня работает на Lolillop. Чтобы этот код работал со старым SDK для Android, вы можете сделать этот код:

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

Ответ 6

Как включить громкоговоритель после автоматического ответа на вызовы.

Я решил проблему выше с помощью setSpeakerphoneOn. Я думаю, что его стоит опубликовать здесь, так как вариант использования автоответчика для телефонного звонка часто также требует использования громкой связи. Еще раз спасибо всем на эту тему, какая прекрасная работа.

Это работает для меня на Android 5.1.1 на моем Nexus 4 без ROOT.;)

Требуется разрешение:

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

Ответ 7

с помощью команд adb Как подобрать звонок по adb

Имейте в виду, что Android - это Linux с массивной JVM на передней панели. Вы можете загрузить приложение командной строки и укоротить телефон, и теперь у вас есть обычный Linux-компьютер и командная строка, которые выполняют все обычные вещи. Запустите скрипты, вы можете даже ssh к нему (трюк OpenVPN)

Ответ 9

проверьте это: сначала добавьте разрешения, затем используйте 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();
    }
}

Ответ 10

FYI, если вам интересно, как закончить текущий вызов на Android O, Valter 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);
    }
}