ESPN DeportesEstados Unidos se queda con la victoria en el juego amistoso contra CanadáPunchFG clears N758bn pension arrears, 957,045 benefitInquirerBohol crop losses climb to P173-M as drought worsenESPNFollow live: Brewers, Padres battling in tight NLDS Game 3The Jerusalem PostMali troops re-enter strategic northern town in years-long fight against insurgentsRapplerLIVE UPDATES: Impeachment trial of Vice President Sara DuterteUOLHouthis atacam aeroporto de Aden, no Iêmen, com drones e mísseis, diz imprensa estatalZDF heuteAktuelle Pressemitteilungen des ZDFNew Straits TimesPBAPP plans hybrid strategy to reduce Penang's reliance on Sungai Muda20 MinutenNews-Scout entdeckt Drei-Meter-Koloss am StrandObservador DesportoAs notícias das 4hBBC عربيالتحالف السعودي يتتبع منصة إطلاق صواريخ في صنعاء تمهيداً لتدميرها
The Daily Newsstand · Free, Always
Wednesday, October 7, 2026

Хватит блокировать спам. Пусть все звонки проходят

Translate

Антиспам годами играет в одну и ту же игру: спамер звонит, система пытается понять, кто это, сверяет номер с базой, смотрит жалобы, рисует предупреждение, иногда блокирует. Спамер меняет номер, покупает новую пачку SIM-карт и продолжает. В ответ индустрия строит ещё более сложные фильтры. Всё это выглядит как технологический прогресс, но проблема остаётся прежней: звонок уже состоялся как событие. Телефон зазвонил, экран загорелся, человек отвлёкся.

А я предлагаю сделать наоборот: пусть проходят вообще все звонки.

Не фильтровать их на входе. Не пытаться идеально понять, кто звонит. Не строить ещё одну базу плохих номеров. Пусть робот дозванивается. Пусть оператор дозванивается. Пусть система массового обзвона получает свой любимый статус ANSWERED.

Только есть одна проблема: ответа человека там может уже не быть.

Вся телефонная индустрия массового обзвона работает с измеримыми результатами: количеством попыток, процентом ответов, продолжительностью соединений, переводами на операторов и конверсией. У современных систем есть механизмы определения автоответчиков и отсутствия реакции, но сам факт принятого вызова остаётся важным сигналом. На нём строятся дальнейшие действия и статистика.

Мы собираемся испортить именно этот сигнал.

ANSWERED != HUMAN

Вот и вся идея.

Телефон может принять вызов сам и оставить его жить в фоне. Без рингтона. Без вибрации. Без поднятия экрана. Без полноэкранного интерфейса поверх того, чем занят пользователь. Для телефонной сети соединение состоялось. Для человека ничего не произошло.

Спамер хотел дозвониться — он дозвонился.

Ну вот и поговорите теперь между собой.

Его робот может бодро начать: «Добрый день, вам одобрено...». Может ждать DTMF, несколько раз сказать «алло» или перевести вызов живому оператору. Всё это уже его проблема. Если система обнаружит тишину и завершит разговор, она потратит попытку. Если продолжит ждать ответа, потратит ещё и время.

Раньше спамеры покупали доступ к нашему вниманию за стоимость телефонного вызова. Теперь они могут купить себе разговор с пустотой.

Мы не блокируем их. Мы даже не спорим с ними. Мы просто больше не считаем сам факт звонка достаточной причиной, чтобы отдавать им человека.

Где здесь технический трюк

Если приложение является системным dialer'ом и получает вызовы через Android InCallService, привычная логика на уровне приложения выглядит примерно так:

override fun onCallAdded(call: Call) {
    super.onCallAdded(call)

    if (call.state != Call.STATE_RINGING) return

    showIncomingCallUi(call)
}

То есть появился звонок — немедленно превращаем его в событие для пользователя. Мы запускаем интерфейс, будим экран, показываем уведомление, создаём ситуацию, в которой человек должен принять решение.

Но сама операция приёма вызова от этого интерфейса отделена.

override fun onCallAdded(call: Call) {
    super.onCallAdded(call)

    if (call.state != Call.STATE_RINGING) return

    if (shouldAnswerSilently(call)) {
        call.answer(VideoProfile.STATE_AUDIO_ONLY)
        return
    }

    showIncomingCallUi(call)
}

Это намеренно схематичный Kotlin-код, а не готовая реализация невидимого звонка. shouldAnswerSilently() здесь обозначает выбранное правило обработки вызова, а не очередной классификатор спама.

Самое важное здесь даже не answer().

Самое важное — отсутствие привычного пользовательского сценария.

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

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

При этом Android не предоставляет магического режима полной невидимости. Системный dialer обязан корректно обслуживать жизненный цикл вызова, соблюдать требования платформы, учитывать системные индикаторы, уведомления и работу аудиотракта. Принять вызов через Call.answer() можно, но сделать это незаметно для пользовательского сценария на конкретном устройстве — отдельная инженерная задача.

Именно поэтому следующий эксперимент должен проверить не то, вызывается ли метод answer(), а можно ли реализовать фоновое соединение достаточно тихо и надёжно, не нарушая нормальную работу телефона.

Теперь испортим им математику

Представим систему обзвона, которая совершает 100 000 попыток. Для простоты допустим, что без нашего механизма 30 000 попыток приводят к соединению с реальным человеком. Это условная модель, а не результаты испытаний.

Теперь представим, что остальные 70 000 попыток включают некоторое количество телефонов, которые способны отвечать автоматически, не подключая пользователя. Пусть таких дополнительных фоновых ответов окажется 20 000.

Картина меняется:

Метрика

До

После

Попытки вызова

100 000

100 000

Соединения с человеком

30 000

30 000

Фоновые соединения

0

20 000

Всего принятых вызовов

30 000

50 000

Доля людей среди принятых вызовов

100%

60%

Смотрите, что произошло. Количество реальных людей вообще не увеличилось. Но система обзвона получила на 20 000 принятых вызовов больше.

Теперь каждый принятый звонок становится менее информативным. Раньше в этой упрощённой модели за ним всегда стоял человек. Теперь за четырьмя из десяти принятых вызовов никого нет.

Это не означает, что оператор автоматически потеряет 40% прибыли. Некоторые платформы распознают тишину, обнаруживают автоответчики и достаточно быстро прекращают бесполезный вызов. Но для этого им уже приходится выполнять дополнительную работу, а простой статус соединения теряет часть своей ценности.

Именно эту асимметрию я считаю интересной.

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

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

Но сама постановка задачи уже изменилась.

Раньше мы пытались обнаружить спамера до ответа.

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

Пусть теперь они учатся отличать человека от телефона

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

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

Прекрасно.

Пусть теперь они учатся отличать человека от телефона. Мы свою часть работы уже закончили.

Зачем нам продолжать бесконечно вычислять, кто из них хороший, а кто плохой, если можно перестать предоставлять человеческое внимание в ответ на любой технически успешный вызов?

И здесь важно не перепутать цель. Мы не стремимся помешать человеку связаться с человеком. Мы стремимся уничтожить привилегию, по которой совершенно незнакомый абонент может одним набором номера потребовать немедленной реакции.

Разумеется, не каждый незнакомый номер — спамер. Это может быть курьер, врач, новый клиент или человек, которому действительно необходимо поговорить. Если бездумно принимать все неизвестные вызовы в тишину, можно потерять важный разговор. Поэтому вопрос о том, какие вызовы направлять в обычный сценарий, а какие оставлять в фоне, остаётся открытым и требует аккуратной реализации.

Но это не причина отказываться от самой идеи. Это причина отделить маршрутизацию вызова от его фактического принятия.

Здесь я намеренно не предлагаю очередную систему распознавания мошенников. Правило выбора тихого режима — самостоятельная инженерная задача. Концепция заключается в другом: телефон может установить соединение, не предоставляя звонящему доступ к человеку.

Концепция безразличия

Самое забавное во всей этой истории — нам вообще не нужно разговаривать со спамером.

Не нужен синтезированный голос, который сообщит: «Ваш звонок очень важен для нас». Не нужно заставлять робота нажимать единицу или просить представиться. Не нужен голосовой помощник, который будет вежливо объяснять, почему владелец телефона занят.

Всё это снова превращает нежелательный звонок в событие, которое наша система обязана обслужить.

А я предлагаю кое-что неприятнее.

Никакого диалога.

Никаких объяснений.

Никакой реакции.

Пользователь продолжает заниматься своими делами. Работает, читает, пишет код, смотрит фильм. Где-то в телефонной сети установилось соединение, кто-то начал говорить, кто-то ждёт ответа, кто-то смотрит на таймер разговора.

Но это больше не означает, что человеку пришлось хотя бы повернуть голову.

Мы привыкли, что спамеры заставляют нас защищаться. Устанавливать определители, включать блокировки, обновлять базы, принимать решения и объяснять, почему мы не желаем разговаривать.

Теперь защищаться от неопределённости придётся другой стороне.

Вы можете звонить сколько угодно. Мы больше не обязаны присутствовать на другом конце.

И если эта концепция действительно заработает массово, её главное достижение будет не в количестве заблокированных номеров. Возможно, заблокированных номеров вообще не окажется.

Главным результатом станет то, что звонок потеряет свою прежнюю власть над вниманием человека.

На этом теория закончена. Дальше — ROLE_DIALER, InCallService, Call.answer() и проверка того, насколько далеко Android позволяет зайти с таким подходом. Без магии, без голосовых секретарей и без обещаний, которые не подтверждены кодом.

Спамеры хотели, чтобы мы брали трубку. Хорошо. Будем брать.

Сначала я думал написать отдельно пример как это реализовать, но после текста Вам должно просто стать интересно, и я приведу примеры тут так как я вижу. И я просто грубо скрестил 2 статьи, поэтому наша история будет длинной.

Пусть звонят. Теперь заставим Android отвечать вместо нас

Мы предложили перестать блокировать телефонный спам. Не пытаться определять мошенников по номеру, не собирать бесконечные чёрные списки и не строить ещё один классификатор, который завтра придётся обновлять. Вместо этого мы решили сделать нечто гораздо более неприятное для систем массового обзвона: разрешить им дозваниваться, но не гарантировать присутствия человека на другом конце.

Теперь пора перевести эту наглость на Kotlin.

Задача простая: входящий звонок должен технически состояться, но не обязан становиться событием для владельца телефона. Никаких голосовых секретарей, синтезированной речи или предложений нажать единицу. Просто соединение, за которым никого нет. Именно это мы назвали концепцией безразличия.

В прошлый раз всё закончилось формулой:

ANSWERED != HUMAN

Сегодня разберём, как близко к ней можно подобраться на обычном Android, не переписывая прошивку и не получая системные привилегии.

Начнём с того, кто вообще управляет звонком

Обычное Android-приложение не может свободно принимать GSM-вызовы. Для этого Android предоставляет Telecom Framework, а пользователь должен назначить приложение системной звонилкой через ROLE_DIALER.

Это принципиальный момент. Мы не собираемся использовать Accessibility Service, искать координаты зелёной кнопки на экране или эмулировать нажатия. Зачем ломиться через окно, если операционная система предоставляет дверь?

Для начала регистрируем собственный InCallService:

<service
    android:name=".telecom.SilentInCallService"
    android:exported="true"
    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>

Нам также понадобится Activity, способная обрабатывать ACTION_DIAL, и полноценный интерфейс управления обычными вызовами. Поэтому приведённый манифест — только фрагмент, а не всё приложение.

Для Android 10 и новее роль запрашивается через RoleManager:Для Android 10 и новее роль запрашивается через RoleManager:

val roleManager =
    getSystemService(RoleManager::class.java)

if (
    roleManager.isRoleAvailable(RoleManager.ROLE_DIALER) &&
    !roleManager.isRoleHeld(RoleManager.ROLE_DIALER)
) {
    val intent = roleManager.createRequestRoleIntent(
        RoleManager.ROLE_DIALER
    )

    dialerRoleLauncher.launch(intent)
}

Здесь dialerRoleLauncher — заранее зарегистрированный Activity Result launcher. Роль назначает сам пользователь через системный диалог. Никакого обхода разрешений нет.

После назначения роли Android начинает передавать вызовы нашему InCallService. Мы получаем объект Call, его состояние и возможность управлять разговором.

Вот тут обычная звонилка и наша концепция расходятся.

Один вызов — два разных сценария

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

Мы хотим разделить две операции, которые обычно воспринимаются как единое действие: установить соединение и привлечь внимание пользователя.

override fun onCallAdded(call: Call) {
    super.onCallAdded(call)

    if (call.state != Call.STATE_RINGING) return

    showIncomingCallUi(call)
}

Так выглядит упрощённый обычный сценарий.

Теперь изменим его:

override fun onCallAdded(call: Call) {
    super.onCallAdded(call)

    if (call.state != Call.STATE_RINGING) return

    if (SilentExperiment.enabled) {
        call.answer(VideoProfile.STATE_AUDIO_ONLY)
        return
    }

    showIncomingCallUi(call)
}

Вот тот самый технический переворот. Мы не спрашиваем пользователя, желает ли он отвечать. Мы просим Android принять вызов самостоятельно.

Но здесь легко обмануть самого себя. Вызвать answer() не означает гарантированно установить соединение. Вызов может завершиться, состояние может измениться, а система может ограничить поведение приложения.

Поэтому первый инженерный принцип: мы верим не вызванному методу, а фактическому состоянию звонка.

Собираем минимальный эксперимент

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

object SilentExperiment {
    @Volatile
    var enabled: Boolean = false
}

Теперь напишем InCallService, который зарегистрирует состояния вызова и выполнит автоматический ответ только в безопасных условиях эксперимента:

class SilentInCallService : InCallService() {

    private val callbacks =
        mutableMapOf<Call, Call.Callback>()

    override fun onCallAdded(call: Call) {
        super.onCallAdded(call)

        val callback = object : Call.Callback() {
            override fun onStateChanged(
                call: Call,
                state: Int
            ) {
                Log.d(
                    "SilentCall",
                    "state=${stateName(state)}"
                )
            }
        }

        callbacks[call] = callback
        call.registerCallback(callback)

        if (
            SilentExperiment.enabled &&
            call.state == Call.STATE_RINGING &&
            calls.size == 1
        ) {
            answerInBackground(call)
        } else {
            showNormalCallUi(call)
        }
    }

    private fun answerInBackground(call: Call) {
        Log.d(
            "SilentCall",
            "Requesting silent answer"
        )

        setMuted(true)

        call.answer(
            VideoProfile.STATE_AUDIO_ONLY
        )
    }

    override fun onCallRemoved(call: Call) {
        callbacks.remove(call)?.let { callback ->
            call.unregisterCallback(callback)
        }

        super.onCallRemoved(call)
    }

    private fun stateName(state: Int): String =
        when (state) {
            Call.STATE_RINGING -> "RINGING"
            Call.STATE_ACTIVE -> "ACTIVE"
            Call.STATE_DISCONNECTED -> "DISCONNECTED"
            Call.STATE_HOLDING -> "HOLDING"
            else -> "OTHER($state)"
        }

    private fun showNormalCallUi(call: Call) {
        // Штатный интерфейс системной звонилки.
    }
}

Это минимальная демонстрация принципа, а не готовый production-сервис. Например, setMuted(true) влияет на состояние микрофона, которое необходимо отслеживать и корректно восстанавливать при завершении вызова. Полноценной реализации также нужны обработка конкурирующих вызовов, системное управление разговором и корректный жизненный цикл сервиса.

И здесь есть важная деталь, которую я не собираюсь прятать под ковёр: отключить микрофон программно — не то же самое, что гарантировать полную изоляцию окружающего звука при любом сбое. Поэтому эксперимент с автоматическим ответом должен проводиться на тестовом устройстве и с контролируемым входящим звонком.

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

Где исчезает звонок

Теперь посмотрим на InCallActivity.

Точнее, на её отсутствие.

В обычной реализации входящего звонка разработчик поднимает Activity поверх экрана блокировки, включает отображение и сопровождает это состоянием интерфейса. Всё правильно: человеку звонят, и система пытается донести до него эту информацию.

Но в режиме фонового ответа приложение может не запускать собственную Activity вообще.

if (SilentExperiment.enabled) {
    answerInBackground(call)
    return
}

showNormalCallUi(call)

В этом return и находится вся концепция.

Мы не продолжаем обычный путь привлечения внимания. Не создаём новую пользовательскую задачу. Не требуем прочитать уведомление или принять решение.

Однако стоит понимать границы платформы. Отказ приложения запускать Activity не означает, что Android полностью скроет активный разговор. Системные индикаторы, маршрутизация звука, управление звонком и требования к интерфейсу действуют независимо от нашего желания.

Мы не пытаемся обмануть Android и спрятать факт работающей телефонии от самого владельца устройства. Мы пытаемся убрать ненужное прерывание, сохраняя системную управляемость и возможность немедленно завершить соединение.

Это разные вещи.

Не верьте Logcat на слово

Если в логах появилась строка:

Requesting silent answer

это ещё не победа. Мы просто вызвали метод.

Нам нужно увидеть действительный переход:

RINGING
   |
   v
ACTIVE
   |
   v
DISCONNECTED

Причём ACTIVE должен подтвердиться не только на принимающем телефоне. На втором устройстве вызывающий абонент должен действительно увидеть установленное соединение.

Для измерений добавим простой журнал времени:

class CallTrace {

    private val startedAt =
        SystemClock.elapsedRealtime()

    fun mark(event: String) {
        val elapsed =
            SystemClock.elapsedRealtime() - startedAt

        Log.d(
            "SilentCall",
            "t=${elapsed}ms event=$event"
        )
    }
}

Вызов mark("ANSWER_REQUESTED") фиксирует запрос на принятие, а mark("ACTIVE") — подтверждённый переход вызова в активное состояние. Это позволит отделить время работы приложения от времени, которое требуется самому Telecom Framework.

Но и здесь нам не нужны красивые числа ради красивых чисел. Основные проверки вообще не про миллисекунды.

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

Если всё это выполняется, эксперимент подтверждает главное: приложение способно отделить технический ответ от привычного сценария привлечения внимания.

Если нет — значит, мы получили конкретное ограничение платформы, а не повод выдумывать успешный результат.

А теперь самое неприятное для массового обзвона

Допустим, мы добились желаемого поведения.

Телефон лежит на столе, звонящий получает соединение, робот запускает сценарий, а пользователь даже не отвлекается.

В обычной модели спамер может рассчитывать на то, что определённая часть принятых вызовов приведёт к разговору. На этом строится экономика обзвона: расходы на линии, длительность попыток, загрузка операторов и ожидаемая конверсия.

Теперь среди принятых вызовов появляются соединения, за которыми нет человека.

Да, современные системы умеют обнаруживать тишину. Да, робот может завершить такой звонок через секунду. И нет, из одного Call.answer() не следует автоматическое разорение колл-центра.

Но сам факт установленного соединения становится менее информативным.

Раньше наша сторона тратила ресурсы на распознавание спамера. Теперь инициатору вызова приходится дополнительно проверять, находится ли на другом конце человек.

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

Именно поэтому я называю этот подход концепцией безразличия.

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

Почему я не добавляю сюда классификатор

Потому что тогда мы вернёмся туда, откуда начали.

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

Мне интереснее другая граница: соединение не равно человеческому вниманию.

Разумеется, важный неизвестный звонок нельзя бездумно отправлять в пустоту. Человек может звонить впервые, и у него может не быть второго канала связи. Поэтому рабочей реализации понадобится понятное правило выбора фонового сценария, а также возможность пользователя восстановить обычную доступность.

Но само правило не обязано превращаться в огромную интеллектуальную систему. И его выбор не меняет того, что мы проверяем в этом эксперименте.

Мы проверяем не спамера.

Мы проверяем телефон.

Может ли он принять соединение без участия владельца? Может ли не превращать каждый входящий вызов в полноэкранное событие? Может ли сохранить конфиденциальность микрофона и нормальную работу связи?

Вот три вопроса, на которые должны ответить реальные испытания.

Соединение состоялось. Человека нет.

Мы слишком долго относились к телефонному звонку как к чему-то, на что обязательно требуется реакция. Принять, сбросить, отклонить, пожаловаться, заблокировать, проверить определитель.

Хотя технически это всего лишь запрос на соединение.

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

Самое интересное здесь даже не то, получится ли испортить статистику конкретного колл-центра. Настоящее изменение заключается в другом: мы перестаём считать входящий звонок безусловным требованием человеческого внимания.

А если таких телефонов станет достаточно много, системам массового обзвона придётся учитывать эту новую реальность.

Пусть проверяют тишину. Пусть совершенствуют алгоритмы. Пусть пытаются понять, действительно ли кто-то взял трубку.

Раньше мы пытались определить, кто звонит нам.

Теперь пусть они пытаются определить, есть ли вообще кто-нибудь на другом конце.

Спамеры хотели, чтобы мы брали трубку. Мы взяли. Теперь разговаривайте.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.