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
    )
}

通話を追加

CallAttributesCompat とコールバックで callsManager.addCall を使用して、システムに新しい呼び出しを追加し、リモート サーフェスの更新を管理します。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 */ }
}

通話を切る

disconnect() を使用して通話を終了し、DisconnectCause で通話を再開します。

disconnect(DisconnectCause(DisconnectCause.LOCAL))

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

CallControlScope 内の currentCallEndpointavailableEndpointsisMuted Flow を使用して、音声エンドポイントをモニタリングして管理する

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)を実装する必要があります。

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

Lambda の正常な完了は、コマンドが処理されたことを示します。コマンドを実行できない場合は、ラムダは例外をスローする必要があります。

適切に実装することで、さまざまなデバイスでシームレスに通話コントロールできます。さまざまなリモート サーフェスでテストを徹底します。

電話番号表示オプション

このライブラリは、通話の状態と音声ルートを管理するだけでなく、通話エクステンションもサポートしています。通話エクステンションは、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)
        }
    }