Обновление с Android 8.0 Oreo
Несмотря на то, что изначально вопрос задавался о поддержке Android L, люди, похоже, все еще задают этот вопрос и ответ, поэтому стоит описать улучшения, представленные в Android 8.0 Oreo. Методы обратной совместимости все еще описаны ниже.
Что изменилось?
Начиная с Android 8.0 Oreo , группа разрешений PHONE также содержит разрешение ANSWER_PHONE_CALLS . Как следует из названия разрешения, его удержание позволяет вашему приложению программно принимать входящие вызовы через соответствующий вызов API без какого-либо взлома системы с использованием отражения или имитации пользователя.
Как мы используем это изменение?
Вам следует проверять версию системы во время выполнения, если вы поддерживаете более старые версии Android, чтобы вы могли инкапсулировать этот новый вызов API, сохраняя поддержку этих старых версий Android. Вам следует выполнить запрос разрешений во время выполнения, чтобы получить это новое разрешение во время выполнения, как это стандартно для новых версий Android.
После получения разрешения ваше приложение просто должно вызвать метод acceptRingingCall TelecomManager . Тогда базовый вызов выглядит следующим образом:
TelecomManager tm = (TelecomManager) mContext
.getSystemService(Context.TELECOM_SERVICE);
if (tm == null) {
throw new NullPointerException("tm == null");
}
tm.acceptRingingCall();
Метод 1: TelephonyManager.answerRingingCall ()
Когда у вас есть неограниченный контроль над устройством.
Что это?
Существует TelephonyManager.answerRingingCall (), который является скрытым внутренним методом. Он работает как мост для ITelephony.answerRingingCall (), который обсуждался в Интернете и с самого начала кажется многообещающим. Это не доступно на 4.4.2_r1 , как она была введена только в фиксации 83da75d для Android 4.4 KitKat ( строка 1537 на 4.4.3_r1 ) , а затем «повторно» в фиксации f1e1e77 для Lollipop ( линии 3138 на 5.0.0_r1 ) из - за того , как Структурировано дерево Git. Это означает, что если вы не поддерживаете только устройства с Lollipop, что, вероятно, является плохим решением, учитывая крошечную долю рынка на данный момент, вам все равно необходимо предоставить запасные методы, если вы пойдете по этому пути.
Как бы мы это использовали?
Поскольку рассматриваемый метод скрыт от использования приложениями SDK, вам необходимо использовать отражение для динамического изучения и использования метода во время выполнения. Если вы не знакомы с отражением, вы можете быстро прочитать Что такое отражение и почему оно полезно? . Вы также можете более подробно изучить особенности в Trail: The Reflection API, если вам это интересно.
И как это выглядит в коде?
final String LOG_TAG = "TelephonyAnswer";
TelephonyManager tm = (TelephonyManager) mContext
.getSystemService(Context.TELEPHONY_SERVICE);
try {
if (tm == null) {
throw new NullPointerException("tm == null");
}
tm.getClass().getMethod("answerRingingCall").invoke(tm);
} catch (Exception e) {
Log.e(LOG_TAG, "Unable to use the Telephony Manager directly.", e);
}
Это слишком хорошо, чтобы быть правдой!
Собственно, есть одна небольшая проблема. Этот метод должен быть полностью функциональным, но менеджер безопасности хочет, чтобы вызывающие абоненты сохраняли android.permission.MODIFY_PHONE_STATE . Это разрешение относится только к частично задокументированным функциям системы, поскольку третьи стороны не должны трогать его (как вы можете видеть из документации к нему). Вы можете попробовать добавить <uses-permission>
для него, но это не поможет, поскольку уровень защиты для этого разрешения - signature | system ( см. Строку 1201 core / AndroidManifest на 5.0.0_r1 ).
Вы можете прочитать Документацию о проблеме 34785: Обновление android: protectionLevel, которая была создана еще в 2012 году, чтобы увидеть, что нам не хватает деталей о конкретном «синтаксисе канала», но, поэкспериментировав, выяснилось, что он должен работать как «И», что означает все указанные флаги должны быть выполнены для предоставления разрешения. При таком предположении это будет означать, что у вас должно быть приложение:
Устанавливается как системное приложение.
Это должно быть нормально и может быть выполнено, попросив пользователей установить с помощью ZIP-архива при восстановлении, например, при рутировании или установке приложений Google на пользовательские ПЗУ, которые еще не упакованы.
Подписан той же подписью, что и frameworks / base, также известный как система, он же ROM.
Вот тут-то и всплывают проблемы. Для этого вам нужно иметь в руках ключи, используемые для подписи frameworks / base. Вам потребуется не только получить доступ к ключам Google для заводских образов Nexus, но и получить доступ ко всем остальным OEM-производителям и ключам разработчиков 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_r4 до 4.4.4_r1 ), но во время локального тестирования код 5 работал, чтобы ответить на входящий звонок. Поскольку Lollipo представляет собой массовое обновление, понятно, что здесь также изменились внутренние компоненты.
Это приводит к команде service call phone 5
.
Как это использовать программно?
Ява
Следующий код является приблизительной реализацией, призванной служить доказательством концепции. Если вы действительно хотите продолжить и использовать этот метод, вы, вероятно, захотите ознакомиться с инструкциями по беспроблемному использованию 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) {
}
} catch (IOException e) {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Манифест
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>
Для этого действительно нужен root-доступ?
К сожалению, это так. Вы можете попробовать использовать на нем Runtime.exec , но мне не повезло с этим маршрутом.
Насколько это стабильно?
Я рад, что ты спросил. Из-за того, что это не задокументировано, это может нарушаться в разных версиях, как показано на кажущейся разнице в коде выше. Имя службы, вероятно, должно оставаться телефонным в разных сборках, но, насколько нам известно, значение кода может изменяться в нескольких сборках одной и той же версии (внутренние модификации, например, оболочкой OEM), в свою очередь, нарушая используемый метод. Поэтому стоит упомянуть, что тестирование проводилось на Nexus 4 (mako / occam). Я бы лично посоветовал вам не использовать этот метод, но, поскольку я не могу найти более стабильного метода, я считаю, что это лучший вариант.
Исходный метод: назначение кода гарнитуры
Для времен, когда вам нужно успокоиться.
В следующем разделе под сильным влиянием этого ответа по Райли C .
Метод моделирования намерения гарнитуры, опубликованный в исходном вопросе, кажется, транслируется так же, как и следовало ожидать, но, похоже, он не достигает цели ответа на звонок. Хотя кажется, что существует код, который должен обрабатывать эти намерения, о них просто не заботятся, что должно означать, что должны быть какие-то новые контрмеры против этого метода. Журнал также не показывает ничего интересного, и я лично не верю, что копаться в исходном коде Android будет целесообразным только из-за возможности того, что Google внесет небольшое изменение, которое в любом случае легко нарушает используемый метод.
Что мы можем сделать прямо сейчас?
Поведение можно последовательно воспроизвести с помощью входного исполняемого файла. Он принимает аргумент кода ключа, для которого мы просто передаем 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) {
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
Существует хороший общедоступный API для Android 8.0 Oreo и новее.
До Android 8.0 Oreo не было общедоступного API. Внутренние API запрещены или просто без документации. Вы должны действовать осторожно.