EndpointUtils.kt
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.core.telecom.internal.utils
import android.bluetooth.BluetoothDevice
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.P
import android.telecom.CallAudioState
import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.core.telecom.CallEndpointCompat
@RequiresApi(Build.VERSION_CODES.O)
internal class EndpointUtils {
companion object {
fun toCallEndpointCompat(state: CallAudioState): CallEndpointCompat {
val type: Int = mapRouteToType(state.route)
return if (type == CallEndpointCompat.TYPE_BLUETOOTH && SDK_INT >= P) {
BluetoothApi28PlusImpl.getCallEndpointFromAudioState(state)
} else {
CallEndpointCompat(endpointTypeToString(type), type)
}
}
fun toCallEndpointsCompat(state: CallAudioState): List<CallEndpointCompat> {
val endpoints: ArrayList<CallEndpointCompat> = ArrayList()
val bitMask = state.supportedRouteMask
if (hasEarpieceType(bitMask)) {
endpoints.add(
CallEndpointCompat(
endpointTypeToString(CallEndpointCompat.TYPE_EARPIECE),
CallEndpointCompat.TYPE_EARPIECE
)
)
}
if (hasBluetoothType(bitMask)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
endpoints.addAll(BluetoothApi28PlusImpl.getBluetoothEndpoints(state))
} else {
endpoints.add(
CallEndpointCompat(
endpointTypeToString(CallEndpointCompat.TYPE_BLUETOOTH),
CallEndpointCompat.TYPE_BLUETOOTH
)
)
}
}
if (hasWiredHeadsetType(bitMask)) {
endpoints.add(
CallEndpointCompat(
endpointTypeToString(CallEndpointCompat.TYPE_WIRED_HEADSET),
CallEndpointCompat.TYPE_WIRED_HEADSET
)
)
}
if (hasSpeakerType(bitMask)) {
endpoints.add(
CallEndpointCompat(
endpointTypeToString(CallEndpointCompat.TYPE_SPEAKER),
CallEndpointCompat.TYPE_SPEAKER
)
)
}
if (hasStreamingType(bitMask)) {
endpoints.add(
CallEndpointCompat(
endpointTypeToString(CallEndpointCompat.TYPE_STREAMING),
CallEndpointCompat.TYPE_STREAMING
)
)
}
return endpoints
}
private fun hasEarpieceType(bitMap: Int): Boolean {
return (bitMap.and(CallAudioState.ROUTE_EARPIECE)) == CallAudioState.ROUTE_EARPIECE
}
fun hasBluetoothType(bitMap: Int): Boolean {
return (bitMap.and(CallAudioState.ROUTE_BLUETOOTH)) == CallAudioState.ROUTE_BLUETOOTH
}
fun hasWiredHeadsetType(bitMap: Int): Boolean {
return (bitMap.and(CallAudioState.ROUTE_WIRED_HEADSET)
) == CallAudioState.ROUTE_WIRED_HEADSET
}
fun hasSpeakerType(bitMap: Int): Boolean {
return (bitMap.and(CallAudioState.ROUTE_SPEAKER)) == CallAudioState.ROUTE_SPEAKER
}
fun hasStreamingType(bitMap: Int): Boolean {
return (bitMap.and(CallAudioState.ROUTE_STREAMING)) == CallAudioState.ROUTE_STREAMING
}
fun mapRouteToType(route: Int): @CallEndpointCompat.Companion.EndpointType Int {
return when (route) {
CallAudioState.ROUTE_EARPIECE -> CallEndpointCompat.TYPE_EARPIECE
CallAudioState.ROUTE_BLUETOOTH -> CallEndpointCompat.TYPE_BLUETOOTH
CallAudioState.ROUTE_WIRED_HEADSET -> CallEndpointCompat.TYPE_WIRED_HEADSET
CallAudioState.ROUTE_SPEAKER -> CallEndpointCompat.TYPE_SPEAKER
CallAudioState.ROUTE_STREAMING -> CallEndpointCompat.TYPE_STREAMING
else -> CallEndpointCompat.TYPE_UNKNOWN
}
}
fun mapTypeToRoute(route: Int): Int {
return when (route) {
CallEndpointCompat.TYPE_EARPIECE -> CallAudioState.ROUTE_EARPIECE
CallEndpointCompat.TYPE_BLUETOOTH -> CallAudioState.ROUTE_BLUETOOTH
CallEndpointCompat.TYPE_WIRED_HEADSET -> CallAudioState.ROUTE_WIRED_HEADSET
CallEndpointCompat.TYPE_SPEAKER -> CallAudioState.ROUTE_SPEAKER
CallEndpointCompat.TYPE_STREAMING -> CallAudioState.ROUTE_STREAMING
else -> CallAudioState.ROUTE_EARPIECE
}
}
fun endpointTypeToString(endpointType: Int): String {
return when (endpointType) {
CallEndpointCompat.TYPE_EARPIECE -> "EARPIECE"
CallEndpointCompat.TYPE_BLUETOOTH -> "BLUETOOTH"
CallEndpointCompat.TYPE_WIRED_HEADSET -> "WIRED_HEADSET"
CallEndpointCompat.TYPE_SPEAKER -> "SPEAKER"
CallEndpointCompat.TYPE_STREAMING -> "EXTERNAL"
else -> "UNKNOWN ($endpointType)"
}
}
}
@RequiresApi(34)
object Api34PlusImpl {
@JvmStatic
@DoNotInline
fun toCallEndpointCompat(endpoint: android.telecom.CallEndpoint):
CallEndpointCompat {
return CallEndpointCompat(
endpoint.endpointName,
endpoint.endpointType,
endpoint.identifier
)
}
@JvmStatic
@DoNotInline
fun toCallEndpointsCompat(endpoints: List<android.telecom.CallEndpoint>):
List<CallEndpointCompat> {
val res = ArrayList<CallEndpointCompat>()
for (e in endpoints) {
res.add(CallEndpointCompat(e.endpointName, e.endpointType, e.identifier))
}
return res
}
@JvmStatic
@DoNotInline
fun toCallEndpoint(e: CallEndpointCompat): android.telecom.CallEndpoint {
return android.telecom.CallEndpoint(e.name, e.type, e.identifier)
}
}
@RequiresApi(28)
object BluetoothApi28PlusImpl {
@JvmStatic
@DoNotInline
fun getBluetoothEndpoints(state: CallAudioState):
ArrayList<CallEndpointCompat> {
val endpoints: ArrayList<CallEndpointCompat> = ArrayList()
val supportedBluetoothDevices = state.supportedBluetoothDevices
for (bluetoothDevice in supportedBluetoothDevices) {
endpoints.add(getCallEndpointFromBluetoothDevice(bluetoothDevice))
}
return endpoints
}
@JvmStatic
@DoNotInline
fun getCallEndpointFromBluetoothDevice(btDevice: BluetoothDevice?): CallEndpointCompat {
var endpointName: String = "Bluetooth Device"
var endpointIdentity: String = "Unknown Address"
if (btDevice != null) {
endpointIdentity = btDevice.address
try {
endpointName = btDevice.name
} catch (e: SecurityException) {
// pass through
}
}
return CallEndpointCompat(
endpointName,
CallEndpointCompat.TYPE_BLUETOOTH,
endpointIdentity
)
}
@JvmStatic
@DoNotInline
fun getCallEndpointFromAudioState(state: CallAudioState): CallEndpointCompat {
return getCallEndpointFromBluetoothDevice(state.activeBluetoothDevice)
}
}
}