Обзор телекоммуникационной структуры

Платформа Android Telecom (также известная как Telecom) управляет аудио- и видеовызовами на устройстве под управлением Android. Сюда входят вызовы на основе SIM-карты, например вызовы, использующие структуру телефонии, и вызовы VoIP, реализующие API ConnectionService .

Основными компонентами, которыми управляет Telecom, являются ConnectionService и InCallService .

Реализация ConnectionService использует такие технологии, как VoIP, для соединения вызовов с другими сторонами. Наиболее распространенной реализацией ConnectionService на телефоне является Телефонный ConnectionService . Он соединяет вызовы оператора связи.

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

Телеком действует как коммутатор. Он маршрутизирует вызовы, предоставляемые реализациями ConnectionService , в вызывающие пользовательские интерфейсы, предоставляемые реализациями InCallService .

Возможно, вам захочется внедрить Telecom API по следующим причинам:

Создайте замену приложения для телефона

Чтобы создать замену телефонному приложению по умолчанию на устройстве Android, реализуйте API InCallService . Ваша реализация должна отвечать следующим требованиям:

  • Он не должен иметь возможности вызова и должен состоять исключительно из пользовательского интерфейса для вызова.
  • Он должен обрабатывать все вызовы, о которых знает платформа Telecom, и не делать предположений о характере вызовов. Например, он не должен предполагать, что вызовы являются телефонными вызовами на основе SIM-карты, а также не должен реализовывать ограничения вызовов, основанные на каком-либо одном ConnectionService , например, принудительное применение ограничений телефонии для видеовызовов.

Дополнительные сведения см. в InCallService .

Интегрируйте решение для звонков

Чтобы интегрировать свое решение для звонков в Android, у вас есть следующие варианты:

  • Внедрите самоуправляемый API ConnectionService. Этот вариант идеально подходит для разработчиков автономных приложений для вызовов, которые не хотят отображать свои вызовы в телефонном приложении по умолчанию, а другие вызовы не отображаются в их пользовательском интерфейсе.

    Используя самоуправляемый ConnectionService , вы помогаете своему приложению взаимодействовать не только с собственными телефонными вызовами на устройстве, но и с другими автономными приложениями для вызовов, реализующими этот API. Самоуправляемый API ConnectionService также управляет маршрутизацией и фокусом звука. Подробности см. в разделе Создание приложения для звонков .

  • Внедрить управляемый API ConnectionService. Этот параметр упрощает разработку решения для вызовов, которое использует существующее телефонное приложение устройства для предоставления пользовательского интерфейса для вызовов. Примеры включают стороннюю реализацию служб вызовов SIP и VoIP. Дополнительные сведения см. в getDefaultDialerPackage() .

    ConnectionService сама по себе предоставляет только средства соединения вызовов. Он не имеет связанного пользовательского интерфейса.

  • Реализуйте API InCallService и ConnectionService. Этот вариант идеален, если вы хотите создать собственное решение для вызовов на основе ConnectionService с собственным пользовательским интерфейсом, а также отображать все другие вызовы Android в одном и том же пользовательском интерфейсе. При использовании этого подхода ваша реализация InCallService не должна делать никаких предположений об источниках отображаемых вызовов. Кроме того, ваша реализация ConnectionService должна продолжать работать без установки телефонного приложения по умолчанию в ваш пользовательский InCallService .

Экранные вызовы

Устройства под управлением Android 10 (уровень API 29) или выше позволяют вашему приложению идентифицировать звонки с номеров, которых нет в адресной книге пользователя, как потенциальные спам-вызовы. Пользователи могут выбрать автоматическое отклонение спам-вызовов. Чтобы обеспечить большую прозрачность для пользователей в случае пропуска вызовов, информация об этих заблокированных вызовах записывается в журнал вызовов. Использование API Android 10 устраняет необходимость получения разрешения READ_CALL_LOG от пользователя для обеспечения функции фильтрации вызовов и идентификации вызывающего абонента.

Вы используете реализацию CallScreeningService для проверки вызовов. Вызовите функцию onScreenCall() для любых новых входящих или исходящих вызовов, если номер отсутствует в списке контактов пользователя. Вы можете проверить объект Call.Details для получения информации о вызове. В частности, функция getCallerNumberVerificationStatus() включает информацию от поставщика сети о другом номере. Если статус проверки не пройден, это верный признак того, что звонок поступил с неверного номера или что это потенциальный спам-вызов.

Котлин

class ScreeningService : CallScreeningService() {
    // This function is called when an ingoing or outgoing call
    // is from a number not in the user's contacts list
    override fun onScreenCall(callDetails: Call.Details) {
        // Can check the direction of the call
        val isIncoming = callDetails.callDirection == Call.Details.DIRECTION_INCOMING

        if (isIncoming) {
            // the handle (e.g. phone number) that the Call is currently connected to
            val handle: Uri = callDetails.handle

            // determine if you want to allow or reject the call
            when (callDetails.callerNumberVerificationStatus) {
                Connection.VERIFICATION_STATUS_FAILED -> {
                    // Network verification failed, likely an invalid/spam call.
                }
                Connection.VERIFICATION_STATUS_PASSED -> {
                    // Network verification passed, likely a valid call.
                }
                else -> {
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED.
                }
            }
        }
    }
}

Ява

class ScreeningService extends CallScreeningService {
    @Override
    public void onScreenCall(@NonNull Call.Details callDetails) {
        boolean isIncoming = callDetails.getCallDirection() == Call.Details.DIRECTION_INCOMING;

        if (isIncoming) {
            Uri handle = callDetails.getHandle();

            switch (callDetails.getCallerNumberVerificationStatus()) {
                case Connection.VERIFICATION_STATUS_FAILED:
                    // Network verification failed, likely an invalid/spam call.
                    break;
                case Connection.VERIFICATION_STATUS_PASSED:
                    // Network verification passed, likely a valid call.
                    break;
                default:
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED
            }
        }
    }
}

Настройте функцию onScreenCall() для вызова respondToCall() , чтобы сообщить системе, как реагировать на новый вызов. Эта функция принимает параметр CallResponse , который вы можете использовать, чтобы указать системе заблокировать вызов, отклонить его, как если бы это сделал пользователь, или отключить его. Вы также можете указать системе вообще не добавлять этот вызов в журнал вызовов устройства.

Котлин

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
val response = CallResponse.Builder()
    // Sets whether the incoming call should be blocked.
    .setDisallowCall(false)
    // Sets whether the incoming call should be rejected as if the user did so manually.
    .setRejectCall(false)
    // Sets whether ringing should be silenced for the incoming call.
    .setSilenceCall(false)
    // Sets whether the incoming call should not be displayed in the call log.
    .setSkipCallLog(false)
    // Sets whether a missed call notification should not be shown for the incoming call.
    .setSkipNotification(false)
    .build()

// Call this function to provide your screening response.
respondToCall(callDetails, response)

Ява

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
CallResponse.Builder response = new CallResponse.Builder();
// Sets whether the incoming call should be blocked.
response.setDisallowCall(false);
// Sets whether the incoming call should be rejected as if the user did so manually.
response.setRejectCall(false);
// Sets whether ringing should be silenced for the incoming call.
response.setSilenceCall(false);
// Sets whether the incoming call should not be displayed in the call log.
response.setSkipCallLog(false);
// Sets whether a missed call notification should not be shown for the incoming call.
response.setSkipNotification(false);

// Call this function to provide your screening response.
respondToCall(callDetails, response.build());

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

<service
    android:name=".ScreeningService"
    android:permission="android.permission.BIND_SCREENING_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallScreeningService" />
    </intent-filter>
</service>

Перенаправить вызов

Устройства под управлением Android 10 или более поздней версии управляют намерениями вызова иначе, чем устройства под управлением Android 9 или более ранней версии. В Android 10 и более поздних версиях широковещательная рассылка ACTION_NEW_OUTGOING_CALL устарела и заменена API CallRedirectionService . CallRedirectionService предоставляет интерфейсы, которые можно использовать для изменения исходящих вызовов, совершаемых платформой Android. Например, сторонние приложения могут отменять вызовы и перенаправлять их через VoIP.

Котлин

class RedirectionService : CallRedirectionService() {
    override fun onPlaceCall(
        handle: Uri,
        initialPhoneAccount: PhoneAccountHandle,
        allowInteractiveResponse: Boolean
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        val callShouldProceed = true
        val callShouldRedirect = false
        when {
            callShouldProceed -> {
                placeCallUnmodified()
            }
            callShouldRedirect -> {
                // Update the URI to point to a different phone number or modify the
                // PhoneAccountHandle and redirect.
                redirectCall(handle, initialPhoneAccount, true)
            }
            else -> {
                cancelCall()
            }
        }
    }
}

Ява

class RedirectionService extends CallRedirectionService {
    @Override
    public void onPlaceCall(
            @NonNull Uri handle,
            @NonNull PhoneAccountHandle initialPhoneAccount,
            boolean allowInteractiveResponse
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        // Your app should implement this logic to determine the redirection.
        boolean callShouldProceed = true;
        boolean callShouldRedirect = false;
        if (callShouldProceed) {
            placeCallUnmodified();
        } else if (callShouldRedirect) {
            // Update the URI to point to a different phone number or modify the
            // PhoneAccountHandle and redirect.
            redirectCall(handle, initialPhoneAccount, true);
        } else {
            cancelCall();
        }
    }
}

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

<service
    android:name=".RedirectionService"
    android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallRedirectionService"/>
    </intent-filter>
</service>

Чтобы использовать службу перенаправления, ваше приложение должно запросить роль перенаправления вызовов у RoleManager . При этом пользователю будет задан вопрос, хотят ли они разрешить вашему приложению обрабатывать перенаправления вызовов. Если вашему приложению не предоставлена ​​эта роль, ваша служба перенаправления не используется.

Вам следует проверить, имеет ли ваше приложение эту роль, когда пользователь запускает ваше приложение, чтобы вы могли запросить его при необходимости. Вы запускаете намерение, созданное RoleManager , поэтому убедитесь, что вы переопределили функцию onActivityResult() для обработки выбора пользователя.

Котлин

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            val roleManager = getSystemService(Context.ROLE_SERVICE) as RoleManager
            // Check if the app needs to register call redirection role.
            val shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION)
            if (shouldRequestRole) {
                val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION)
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE)
            }
        }
    }

    companion object {
        private const val REDIRECT_ROLE_REQUEST_CODE = 1
    }
}

Ява

class MainActivity extends AppCompatActivity {
    private static final int REDIRECT_ROLE_REQUEST_CODE = 0;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            RoleManager roleManager = (RoleManager) getSystemService(Context.ROLE_SERVICE);
            // Check if the app needs to register call redirection role.
            boolean shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION);
            if (shouldRequestRole) {
                Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION);
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE);
            }
        }
    }
}