通信

新しい Android Telecom Jetpack ライブラリを使用すると、通話の状態をプラットフォームに簡単に伝えられます。ソースコードとサンプルアプリは GitHub にあります。

依存関係と権限

まず、アプリ モジュールの build.gradle ファイルを開き、androidx Telecom モジュールの依存関係を追加します。

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

アプリ マニフェストで、アプリが MANAGE_OWN_CALLS 権限を使用することを宣言します。

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

アプリケーションを登録する

Android にアプリを認識させるには、アプリとその機能を登録する必要があります。これにより、アプリがサポートしている機能(ビデオ通話、通話のストリーミング、通話の保留など)を Android に指示します。この情報は、Android がアプリの機能と連携するように設定できます。

 private val callsManager = CallsManager(context)

var capabilities: @CallsManager.Companion.Capability Int =
    CallsManager.CAPABILITY_BASELINE or
          CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING or
          CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING

callsManager.registerAppWithTelecom(capabilities)

プラットフォームの統合

通話アプリで最も一般的な通話シナリオは、着信と発信の 2 つです。呼び出しの方向を正しく登録し、ユーザーに適切に通知を通知するには、以下の API を使用します。

通話を登録する

次の例は、着信を登録する方法を示しています。

companion object {
  const val APP_SCHEME = "MyCustomScheme"
  const val ALL_CALL_CAPABILITIES = (CallAttributes.SUPPORTS_SET_INACTIVE
    or CallAttributes.SUPPORTS_STREAM or CallAttributes.SUPPORTS_TRANSFER)

  const val INCOMING_NAME = "Luke"
  val INCOMING_URI: Uri = Uri.fromParts(APP_SCHEME, "", "")
  // Define all possible properties for CallAttributes
  val INCOMING_CALL_ATTRIBUTES =
    CallAttributes(
      INCOMING_NAME,
      INCOMING_URI,
      DIRECTION_INCOMING,
      CALL_TYPE_VIDEO_CALL,
      ALL_CALL_CAPABILITIES)
}

callAttributes オブジェクトには次のプロパティがあります。

  • displayName: 発信者、会議、セッションの名前。
  • address: 通話の住所。これは会議リンクにも応用できます
  • direction: 通話の方向(着信、発信など)。
  • callType: 送信データに関する情報(動画や音声など)。
  • callCapabilities: 呼び出しの機能を指定するオブジェクト。

callCapabilities オブジェクトには次のプロパティがあります。

  • streaming: 通話が別の Android 搭載デバイスへのオーディオ ストリーミングをサポートしているかどうかを示します。
  • transfer: 通話を転送できるかどうかを示します。
  • hold: 通話を保留にできるかどうかを表します。

通話を追加

デバイスが通信をサポートしていない場合、または通話のセットアップ中にエラーが発生した場合、addCall() メソッドは例外を返します。

try {
    callsManager.addCall(
        INCOMING_CALL_ATTRIBUTES,
        onIsCallAnswered, // Watch needs to know if it can answer the call
        onIsCallDisconnected,
        onIsCallActive,
        onIsCallInactive
    ) {
        callControlScope = this
    }
}

電話を受ける

一度着信した通話は、応答するか拒否する必要があります。この試験では、通話に応答する方法を説明します。

when (answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
    is CallControlResult.Success -> {

    }

    is CallControlResult.Error -> {

    }
}

別の呼び出しが進行中の場合、answer()CallControlResult.Error を返し、応答できなかった理由を示します。この場合、ユーザーは他の通話を保留にする必要があります。

通話を拒否する

通話を拒否するには、DisconnectCause.Rejected で通話を切断します。

fun onRejectCall(){
    coroutineScope.launch {
        callControlScope?.let {
            it.disconnect(DisconnectCause(DisconnectCause.REJECTED))
        }
    }
}

発信

発信時にリモート側が応答したら、呼び出しを active に設定して、通話が進行中であることをプラットフォームが認識できるようにする必要があります。

when (setActive()) {
    is CallControlResult.Success -> {
        onIsCallActive()
    }

    is CallControlResult.Error -> {
        updateCurrentCall {
            copy(errorCode = result.errorCode)
        }
    }
}

通話を保留にする

通話アプリで通話の保留がサポートされている場合は、setInActive を使用して、通話がアクティブでなく、他のアプリがマイクとカメラを自由に使用できることをプラットフォームに伝えます。

when (setInActive()) {
    is CallControlResult.Success -> {

    }

    is CallControlResult.Error -> {
        updateCurrentCall {
            copy(errorCode = result.errorCode)
        }
    }
}

接続を解除する

通話を切断するには、有効な原因を指定して、切断するよう通信スタックに通知します。

coroutineScope.launch {
    callControlScope?.disconnect(DisconnectCause(DisconnectCause.LOCAL))
}

音声をルーティングする

通話中に、スピーカー、受話口、Bluetooth デバイスなどのデバイスを切り替える場合があります。ユーザーが使用できるすべてのデバイスとアクティブなデバイスのリストを取得するには、availableEndpoints API と currentCallEndpoint API を使用します。

この例では、両方のフローを組み合わせて UI オブジェクトを作成し、デバイスのリストとアクティブになっているデバイスをユーザーに表示します。

availableEndpoint = combine(callControlScope.availableEndpoints,
    callControlScope.currentCallEndpoint) {
    availableDevices: List<CallEndpoint>, activeDevice : CallEndpoint ->
    availableDevices.map {
        EndPointUI(
            isActive = activeDevice.endpointName == it.endpointName, it
        )
    }
}

アクティブなデバイスを変更するには、requestEndpointChange を使用して、変更後の CallEndpoint を指定します。

coroutineScope.launch {
     callControlScope?.requestEndpointChange(callEndpoint)
}

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

Telecom ライブラリはフォアグラウンドに対応しています。このライブラリは、Android 13 以前を搭載したデバイスに対して ConnectionService を使用します。Android 14 以降では、フォアグラウンド タイプのマイクとカメラを使用して、フォアグラウンド サービスを正しくサポートします。詳しくは、フォアグラウンド サービスをご覧ください。

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

アプリにフォアグラウンド実行の優先順位を設定するには、プラットフォームで呼び出しを登録したら通知を作成します。アプリが通話を終了するか、通知が無効になると、フォアグラウンド優先度は削除されます。

is TelecomCall.Registered -> {
    val notification = createNotification(call)
    notificationManager.notify(TELECOM_NOTIFICATION_ID, notification)
}

サーフェス サポート

スマートウォッチには汎用のエンドポイント レシーバ アプリケーションがあります。このアプリは、通話の応答、拒否、切断などの基本的なインターフェースをユーザーに提供します。アプリは、デバイスでアクションを実行したプラットフォームに通知するラムダ関数を実装することで、これらのアクションをサポートします。

アプリケーションが応答しない場合、各ラムダ関数は 5 秒後にタイムアウトし、トランザクションが失敗すると、

callsManager.addCall(
        attributes,
        onIsCallAnswered, // Watch/Auto need to know if they can answer the call
        onIsCallDisconnected,
        onIsCallActive,
        onIsCallInactive
    ) {
//Call Scope
}
/**
  *  Can the call be successfully answered??
  *  TIP: Check the connection/call state to see if you can answer a call
  *  Example you may need to wait for another call to hold.
  **/
val onIsCallAnswered: suspend(type: Int) -> Unit = {}

/**
  * Can the call perform a disconnect
  */
val onIsCallDisconnected: suspend (cause: DisconnectCause) -> Unit = {}

/**
  *  Check is see if you can make the call active.
  *  Other calls and state might stop us from activating the call
  */
val onIsCallActive: suspend () -> Unit = {
    updateCurrentCall {
    }
}

/**
  * Check to see if you can make the call inactivate
  */
val onIsCallInactive: suspend () -> Unit = {}