Core-Telecom

Core-Telecom ライブラリは、強力で一貫性のある API セットを提供することで、通話アプリケーションと Android プラットフォームの統合プロセスを効率化します。

実際の実装を確認する場合は、GitHub でサンプル アプリケーションをご覧ください。

Core-Telecom を設定する

アプリの build.gradle ファイルに androidx.core:core-telecom 依存関係を追加します。

dependencies {
    implementation ("androidx.core:core-telecom:1.0.0")
}

AndroidManifest.xmlMANAGE_OWN_CALLS 権限を宣言します。

<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />

アプリを登録する

CallsManager を使用して通話アプリを Android に登録し、通話をシステムに追加します。登録時に、アプリの機能(音声、動画のサポートなど)を指定します。

val callsManager = CallsManager(context)

val capabilities: @CallsManager.Companion.Capability Int =
    (CallsManager.CAPABILITY_BASELINE or
          CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING)

callsManager.registerAppWithTelecom(capabilities)

通話管理

Core-Telecom API を使用して、通話ライフサイクルを作成、管理します。

通話を作成する

CallAttributesCompat オブジェクトは、一意の通話のプロパティを定義します。通話には次の特性があります。

  • displayName: 発信者名。
  • address: 通話アドレス(電話番号、会議リンクなど)。
  • direction: 着信または発信。
  • callType: 音声または動画。
  • callCapabilities: 転送と保留をサポートします。

着信通話を作成する例を次に示します。

fun createIncomingCallAttributes(
    callerName: String,
    callerNumber: String,
    isVideoCall: Boolean): CallAttributesCompat {
    val addressUri = Uri.parse("YourAppScheme:$callerNumber")

    // Define capabilities supported by your call.
    val callCapabilities = CallAttributesCompat.CallCapability(
        supportsSetInactive = CallAttributesCompat.SUPPORTS_SET_INACTIVE // Call can be made inactive (implies hold)
    )

    return CallAttributesCompat(
        displayName = callerName,
        address = addressUri,
        direction = CallAttributesCompat.DIRECTION_INCOMING,
        callType = if (isVideoCall) CallAttributesCompat.CALL_TYPE_VIDEO_CALL else CallAttributesCompat.CALL_TYPE_AUDIO_CALL,
        callCapabilitiesCompat = callCapabilities
    )
}

通話を追加する

callsManager.addCallCallAttributesCompat とコールバックとともに使用して、新しい通話をシステムに追加し、リモート サーフェスの更新を管理します。addCall ブロック内の callControlScope を使用すると、主にアプリが通話状態を移行し、音声の更新を受信できます。

try {
    callsManager.addCall(
        INCOMING_CALL_ATTRIBUTES,
        onAnswerCall, // Watch needs to know if it can answer the call.
        onSetCallDisconnected,
        onSetCallActive,
        onSetCallInactive
    ) {
        // The call was successfully added once this scope runs.
        callControlScope = this
    }
}
catch(addCallException: Exception){
   // Handle the addCall failure.
}

着信に応答する

CallControlScope 内で着信に応答します。

when (val result = answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
    is CallControlResult.Success -> { /* Call answered */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

通話を拒否する

CallControlScope 内で DisconnectCause.REJECTED を指定して disconnect() を使用し、通話を拒否します。

disconnect(DisconnectCause(DisconnectCause.REJECTED))

発信通話をアクティブにする

相手が応答したら、発信通話をアクティブに設定します。

when (val result = setActive()) {
    is CallControlResult.Success -> { /* Call active */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

通話を保留にする

setInactive() を使用して通話を保留にします。

when (val result = setInactive()) {
    is CallControlResult.Success -> { /* Call on hold */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

通話を切る

DisconnectCause を指定して disconnect() を使用し、通話を切ります。

disconnect(DisconnectCause(DisconnectCause.LOCAL))

通話音声エンドポイントを管理する

CallControlScope 内の currentCallEndpointavailableEndpointsisMuted Flow を使用して、音声エンドポイントを監視、管理します。Telecom を使用する場合は、AudioManager#setCommunicationDevice API または AudioManager#startBluetoothSco API を使用して音声ルートを管理しないでください。通話で音声の問題が発生します。

fun observeAudioStateChanges(callControlScope: CallControlScope) {
    with(callControlScope) {
        launch { currentCallEndpoint.collect { /* Update UI */ } }
        launch { availableEndpoints.collect { /* Update UI */ } }
        launch { isMuted.collect { /* Handle mute state */ } }
    }
}

requestEndpointChange() を使用して、アクティブなオーディオ機器を変更します。

coroutineScope.launch {
     callControlScope.requestEndpointChange(callEndpoint)
}

フォアグラウンドのサポート

このライブラリは、フォアグラウンドのサポートに ConnectionService(Android 13 API レベル 33 以前)または foregroundtypes(Android 14 API レベル 34 以降)を使用します。

フォアグラウンドの要件として、アプリケーションがフォアグラウンドで実行されていることをユーザーに知らせる通知を投稿する必要があります。

アプリがフォアグラウンド実行の優先度を取得できるようにするには、プラットフォームで通話を追加したら通知を作成します。アプリが通話を終了するか、通知が無効になると、フォアグラウンドの優先度が削除されます。

詳しくは、フォアグラウンド サービスをご覧ください

リモート サーフェスのサポート

リモート デバイス(スマートウォッチ、Bluetooth ヘッドセット、Android Auto)は、スマートフォンを直接操作しなくても通話を管理できます。アプリは、これらのデバイスによって開始されたアクションを処理するために、CallsManager.addCall に提供されるコールバック ラムダ(onAnswerCallonSetCallDisconnectedonSetCallActiveonSetCallInactive)を実装する必要があります。

リモート アクションが発生すると、対応するラムダが呼び出されます。

ラムダが正常に完了すると、コマンドが処理されたことが通知されます。コマンドに従えない場合は、ラムダが例外をスローする必要があります。

適切に実装することで、さまざまなデバイスでシームレスな通話制御が可能になります。 さまざまなリモート サーフェスで徹底的にテストしてください。

電話番号表示オプション

通話の状態と音声ルートの管理に加えて、このライブラリは電話番号表示オプションもサポートしています。これは、Android Auto などのリモート サーフェスでより豊富な通話エクスペリエンスを実現するためにアプリが実装できるオプション機能です。これらの機能には、会議室、通話のミュート、追加の通話アイコンなどがあります。アプリが拡張機能を実装すると、アプリが提供する情報は、これらの拡張機能を UI に表示することもサポートする接続されたすべてのデバイスと同期されます。つまり、これらの機能は、ユーザーが操作できるリモート デバイスでも利用できます。

拡張機能を使用して通話を作成する

通話を作成するときに、CallManager#addCall を使用して通話を作成する代わりに、 CallManager#addCallWithExtensions を使用できます。これにより、 アプリは ExtensionInitializationScope という別のスコープにアクセスできます。このスコープを使用すると、アプリケーションはサポートするオプションの拡張機能のセットを初期化できます。また、このスコープには追加のメソッド onCall が用意されています。このメソッドは、拡張機能の機能交換と初期化が完了した後、CallControlScope をアプリに返します。

scope.launch {
    mCallsManager.addCallWithExtensions(
        attributes,
        onAnswer,
        onDisconnect,
        onSetActive,
        onSetInactive
    ) {
        // Initialize extension-specific code...

        // After the call has been initialized, perform in-call actions
        onCall {
            // Example: process call state updates
            callStateFlow.onEach { newState ->
                // handle call state updates and notify telecom
            }.launchIn(this)

            // Use initialized extensions...
        }
    }
}

通話の参加者をサポートする

アプリが会議やグループ通話の通話参加者をサポートしている場合は、addParticipantExtension を使用してこの拡張機能のサポートを宣言し、関連する API を使用して、参加者が変更されたときにリモート サーフェスを更新します。

mCallsManager.addCallWithExtensions(...) {
        // Initialize extensions...

        // Notifies Jetpack that this app supports the participant
        // extension and provides the initial participants state in the call.
        val participantExtension = addParticipantExtension(
            initialParticipants,
            initialActiveParticipant
        )

        // After the call has been initialized, perform in-call control actions
        onCall {
            // other in-call control and extension actions...

            // Example: update remote surfaces when the call participants change
            participantsFlow.onEach { newParticipants ->
                participantExtension.updateParticipants(newParticipants)
            }.launchIn(this)
        }
    }

通話に参加している参加者をリモート サーフェスに通知するだけでなく、ParticipantExtension#updateActiveParticipant を使用してアクティブな参加者を更新することもできます。

通話の参加者に関連するオプションのアクションもサポートされています。 アプリは ParticipantExtension#addRaiseHandSupport を使用して、通話で参加者が挙手するという概念をサポートし、他の参加者が挙手しているかどうかを確認できます。

mCallsManager.addCallWithExtensions(...) {
        // Initialize extensions...

        // Notifies Jetpack that this app supports the participant
        // extension and provides the initial list of participants in the call.
        val participantExtension = addParticipantExtension(initialParticipants)
        // Notifies Jetpack that this app supports the notion of participants
        // being able to raise and lower their hands.
        val raiseHandState = participantExtension.addRaiseHandSupport(
                initialRaisedHands
            ) { onHandRaisedStateChanged ->
                // handle this user's raised hand state changed updates from
                // remote surfaces.
            }

        // After the call has been initialized, perform in-call control actions
        onCall {
            // other in-call control and extension actions...

            // Example: update remote surfaces when the call participants change
            participantsFlow.onEach { newParticipants ->
                participantExtension.updateParticipants(newParticipants)
            }.launchIn(this)
            // notify remote surfaces of which of the participants have their
            // hands raised
            raisedHandsFlow.onEach { newRaisedHands ->
                raiseHandState.updateRaisedHands(newRaisedHands)
            }.launchIn(this)
        }
    }

通話のミュートをサポートする

通話のミュートを使用すると、ユーザーはデバイスのマイクを物理的にミュートすることなく、アプリに通話の発信音をミュートするようリクエストできます。この機能は通話ごとに管理されるため、VOIP 通話がアクティブな間、Jetpack は進行中の携帯電話通話のグローバル ミュート状態の管理の複雑さを処理します。これにより、複数の通話シナリオで発信音をミュートする際のエラーが少なくなり、ユーザーが通話のミュートが有効になっていることに気づかずに話しているときに「話していますか」などの便利な機能を使用できます。

mCallsManager.addCallWithExtensions(...) {
        // Initialize extensions...

        // Add support for locally silencing the call's outgoing audio and
        // register a handler for when the user changes the call silence state
        // from a remote surface.
        val callSilenceExtension = addLocalCallSilenceExtension(
            initialCallSilenceState = false
        ) { newCallSilenceStateRequest ->
            // handle the user's request to enable/disable call silence from
            // a remote surface
        }

        // After the call has been initialized, perform in-call control actions
        onCall {
            // other in-call control and extension actions...

            // When the call's call silence state changes, update remote
            // surfaces of the new state.
            callSilenceState.onEach { isSilenced ->
                callSilenceExtension.updateIsLocallySilenced(isSilenced)
            }.launchIn(this)
        }
    }

通話アイコンをサポートする

通話アイコンを使用すると、アプリは通話中にリモート サーフェスに表示される通話を表すカスタム アイコンを指定できます。このアイコンは、通話のライフサイクル中に更新することもできます。

mCallsManager.addCallWithExtensions(...) {
        // Initialize extensions...

        // Add support for a custom call icon to be displayed during the
        // lifetime of the call.
        val callIconExtension = addCallIconExtension(
            initialCallIconUri = initialUri
        )

        // After the call has been initialized, perform in-call control actions
        onCall {
            // other in-call control and extension actions...

            // When the call's icon changes, update remote surfaces by providing
            // the new URI.
            callIconUri.onEach { newIconUri ->
                callIconExtension.updateCallIconUri(newIconUri)
            }.launchIn(this)
        }
    }

システムの通話履歴に追加する

アプリの VoIP 通話をシステムの通話履歴に追加すると、システムのダイヤルパッドに表示され、ユーザーはそこから折り返すことができます。詳しくは、 統合された通話履歴をご覧ください。