电信

新的 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 知道您的应用,您必须注册该应用及其 capability。这会告知 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)

平台集成

任何通话应用的两种最常见的通话场景是来电和去电。如需正确注册调用的方向并适当地向用户发送通知,请使用以下 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)
        }
    }
}

断开连接

如需断开通话连接,请提供正当原因以告知 Telecom 堆栈断开连接:

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

转接音频

在通话期间,用户有时会在扬声器、手机听筒或蓝牙设备等设备之间切换。使用 availableEndpointscurrentCallEndpoint API 获取用户可用的所有设备以及哪个设备处于活动状态的列表。

以下示例将两个流程组合起来,创建一个界面对象,以向用户显示设备列表以及哪个设备处于有效状态:

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

Surface 支持

手表具有通用端点接收器应用。此应用可为用户提供基本界面,例如接听、拒接和挂断来电。应用通过实现 lambda 函数来支持这些操作,以通知平台您已在设备上执行操作。

如果您的应用没有响应,则每个 lambda 函数都会在 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 = {}