Android: Программный сброс входящего звонка

72
22 сентября 2021, 13:30

Приложение сбрасывает входящий звонок и сохраняет номер звонящего. Все нормально работало, но появилась проблема на Samsung j3 (Android 7.0), логирование происходит (в приложенном коде убрал его), а звонок не сбрасывается.

В чем может быть проблема?

Пермишены:

<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.PROCESS_INCOMING_CALLS" />
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />

Класс, который должен сбрасывать звонок:

public class CallReceiver extends BroadcastReceiver implements ITelephony {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("android.intent.action.PHONE_STATE")) {
            String phoneState = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
            if (phoneState.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
                ITelephony telephonyService;
                TelephonyManager telephony = (TelephonyManager)
                        context.getSystemService(Context.TELEPHONY_SERVICE);
                try {
                    Class c = Class.forName(telephony.getClass().getName());
                    Method m = c.getDeclaredMethod("getITelephony");
                    m.setAccessible(true);
                    telephonyService = (ITelephony) m.invoke(telephony);
                    if (telephonyService != null)
                        telephonyService.endCall();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    @Override
    public boolean endCall() {
        return false;
    }
    @Override
    public void answerRingingCall() {
    }
    @Override
    public void silenceRinger() {
    }
}

P.S. У меня нет устройства, на котором проблема воспроизводится, на моих девайсах все работает.

UPD: Я изучаю проблему на англоязычном SO, нашел человека с похожей проблемой. Опять же догадка, ибо о проблеме мне сообщил юзер, а у меня нет устройств Самсунг. В его случае при таком же методе сброса звонка возникает ошибка, что нужен пермишен MODIFY_PHONE_STATE, который есть только у системных приложений. В таком случае я не понимаю, почему на устройствах OnePlus 6 и Xiaomi Mi Max 3 с Android 9.0 и Xiaomi Mi Max 2 с Android 8.0 приложение работает без этого пермишена, а на Samsung j3 со "старым" Android 7.0 возникает такая проблема? Как в таком случае на нем работают "черные списки" с Google Play и т.д.? Буду рад любым подсказкам по этой теме.

Answer 1

Приложенный способ не удалось заставить работать на Samsung-устройствах. Как подметили в комментариях, производители часто немного меняют систему, а способ с ITelephony скорее хак, чем нормальное решение, поэтому его неработоспособность на Samsung вполне нормальное явление. Проблема действительно была в пермишене MODIFY_PHONE_STATE, который никак нельзя получить.

Есть рабочее решение для Android 6.0+. Приложение должно заменить стандартное приложение для звонков (default call dialer). Это делается довольно просто, в итоге Android сам будет передавать звонки приложению, а там уже можно их сбрасывать. Прикладываю код на kotlin.

В первую очередь в манифест нужно добавить intent filters в AndroidManifest.xml

<intent-filter>
    <action android:name="android.intent.action.DIAL" />
    <data android:scheme="tel" />
</intent-filter>
<intent-filter>
    <action android:name="android.intent.action.DIAL" />
</intent-filter>

и добавить сервис для обработки звонков

<service
    android:name=".CallService"
    android:permission="android.permission.BIND_INCALL_SERVICE">
    <meta-data
        android:name="android.telecom.IN_CALL_SERVICE_UI"
        android:value="true"/>
            <intent-filter>
                <action android:name="android.telecom.InCallService" />
            </intent-filter>
</service>

Так же при запуске приложения нужно убедиться, что оно установлено как стандартное для звонков и в случае необходимости сделать себя текущим приложением для звонков:

if (getSystemService(TelecomManager::class.java).defaultDialerPackage != packageName) {
    Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER)
            .putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName)
            .let(::startActivity)
}

Код сервиса:

class CallService : InCallService() {
    override fun onCallAdded(call: Call) {
        OngoingCall.call = call
        val number = call.details.handle.schemeSpecificPart // номер телефона
        // тут можно логировать и вообще что угодно
        OngoingCall.hangup() // сбросить вызов
    }
    override fun onCallRemoved(call: Call) {
        OngoingCall.call = null
    }
}

В целом эта тема достаточно хорошо раскрыта, есть статьи, например:

Android default dialer replacement (part I)

Android default dialer replacement (part II)

READ ALSO
Не запускается телеграмм бот на Spring Boot

Не запускается телеграмм бот на Spring Boot

Делал по примеру телеграмм бота, но он у меня не запускаетсяхотя делал 1 в 1

220
Извлечь URL из href с помощью XPath

Извлечь URL из href с помощью XPath

Не получается из href извлечь содержимый URL с помощью XPath

182
Как установить 3D pdf на сайт?

Как установить 3D pdf на сайт?

Подскажите, как установить 3D pdf модель на сайт? Выглядит примерно так: 3D модель можно двигать курсором в разных направленияхСколько искал...

222
Как расположить блоки сайта для планшета и мобилки?

Как расположить блоки сайта для планшета и мобилки?

учусь верстать сайтыНашел макет сайта в интернете

130