/*
* Copyright (C) 2022 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.uwb.impl
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.util.Log
import androidx.core.uwb.RangingCapabilities
import androidx.core.uwb.RangingParameters.Companion.CONFIG_MULTICAST_DS_TWR
import androidx.core.uwb.RangingParameters.Companion.CONFIG_PROVISIONED_MULTICAST_DS_TWR
import androidx.core.uwb.RangingParameters.Companion.CONFIG_PROVISIONED_UNICAST_DS_TWR
import androidx.core.uwb.RangingParameters.Companion.CONFIG_UNICAST_DS_TWR
import androidx.core.uwb.UwbAddress
import androidx.core.uwb.UwbClientSessionScope
import androidx.core.uwb.UwbComplexChannel
import androidx.core.uwb.UwbControleeSessionScope
import androidx.core.uwb.UwbControllerSessionScope
import androidx.core.uwb.UwbManager
import androidx.core.uwb.backend.IUwb
import androidx.core.uwb.exceptions.UwbServiceNotAvailableException
import androidx.core.uwb.helper.checkSystemFeature
import androidx.core.uwb.helper.handleApiException
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.nearby.Nearby
import kotlinx.coroutines.tasks.await
internal class UwbManagerImpl(private val context: Context) : UwbManager {
companion object {
const val TAG = "UwbMangerImpl"
val PUBLIC_AVAILABLE_CONFIG_IDS = setOf(
CONFIG_UNICAST_DS_TWR,
CONFIG_MULTICAST_DS_TWR,
CONFIG_PROVISIONED_UNICAST_DS_TWR,
CONFIG_PROVISIONED_MULTICAST_DS_TWR
)
var iUwb: IUwb? = null
}
init {
val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
iUwb = IUwb.Stub.asInterface(service)
Log.i(TAG, "iUwb service created successfully.")
}
override fun onServiceDisconnected(p0: ComponentName?) {
iUwb = null
}
}
val intent = Intent("androidx.core.uwb.backend.service")
intent.setPackage("androidx.core.uwb.backend")
context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
@Deprecated("Renamed to controleeSessionScope")
override suspend fun clientSessionScope(): UwbClientSessionScope {
return createClientSessionScope(false)
}
override suspend fun controleeSessionScope(): UwbControleeSessionScope {
return createClientSessionScope(false) as UwbControleeSessionScope
}
override suspend fun controllerSessionScope(): UwbControllerSessionScope {
return createClientSessionScope(true) as UwbControllerSessionScope
}
private suspend fun createClientSessionScope(isController: Boolean): UwbClientSessionScope {
checkSystemFeature(context)
val pm = context.packageManager
val hasGmsCore = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
context, /* minApkVersion */230100000) == ConnectionResult.SUCCESS
val isChinaGcoreDevice = pm.hasSystemFeature("cn.google.services") &&
pm.hasSystemFeature("com.google.android.feature.services_updater")
return if (hasGmsCore && !isChinaGcoreDevice) createGmsClientSessionScope(isController)
else createAospClientSessionScope(isController)
}
private suspend fun createGmsClientSessionScope(isController: Boolean): UwbClientSessionScope {
Log.i(TAG, "Creating Gms Client session scope")
val uwbClient = if (isController)
Nearby.getUwbControllerClient(context) else Nearby.getUwbControleeClient(context)
if (!uwbClient.isAvailable().await()) {
Log.e(TAG, "Uwb availability : false")
throw UwbServiceNotAvailableException("Cannot start a ranging session when UWB is " +
"unavailable")
}
try {
val nearbyLocalAddress = uwbClient.localAddress.await()
val nearbyRangingCapabilities = uwbClient.rangingCapabilities.await()
val localAddress = UwbAddress(nearbyLocalAddress.address)
val supportedConfigIds = nearbyRangingCapabilities.supportedConfigIds.toMutableList()
supportedConfigIds.retainAll(PUBLIC_AVAILABLE_CONFIG_IDS)
val rangingCapabilities = RangingCapabilities(
nearbyRangingCapabilities.supportsDistance(),
nearbyRangingCapabilities.supportsAzimuthalAngle(),
nearbyRangingCapabilities.supportsElevationAngle(),
nearbyRangingCapabilities.minRangingInterval,
nearbyRangingCapabilities.supportedChannels.toSet(),
nearbyRangingCapabilities.supportedNtfConfigs.toSet(),
supportedConfigIds.toSet(),
nearbyRangingCapabilities.supportedSlotDurations.toSet(),
nearbyRangingCapabilities.supportedRangingUpdateRates.toSet(),
nearbyRangingCapabilities.supportsRangingIntervalReconfigure(),
nearbyRangingCapabilities.hasBackgroundRangingSupport())
return if (isController) {
val uwbComplexChannel = uwbClient.complexChannel.await()
UwbControllerSessionScopeImpl(
uwbClient,
rangingCapabilities,
localAddress,
UwbComplexChannel(uwbComplexChannel.channel, uwbComplexChannel.preambleIndex)
)
} else {
UwbControleeSessionScopeImpl(
uwbClient,
rangingCapabilities,
localAddress
)
}
} catch (e: ApiException) {
handleApiException(e)
throw RuntimeException("Unexpected error. This indicates that the library is not " +
"up-to-date with the service backend.")
}
}
private fun createAospClientSessionScope(isController: Boolean): UwbClientSessionScope {
Log.i(TAG, "Creating Aosp Client session scope")
val uwbClient = if (isController)
iUwb?.controllerClient else iUwb?.controleeClient
if (uwbClient == null) {
Log.e(TAG, "Failed to get UwbClient. AOSP backend is not available.")
}
try {
val aospLocalAddress = uwbClient!!.localAddress
val aospRangingCapabilities = uwbClient.rangingCapabilities
val localAddress = aospLocalAddress?.address?.let { UwbAddress(it) }
val rangingCapabilities = aospRangingCapabilities?.let {
RangingCapabilities(
it.supportsDistance,
it.supportsAzimuthalAngle,
it.supportsElevationAngle,
it.minRangingInterval,
it.supportedChannels.toSet(),
it.supportedNtfConfigs.toSet(),
it.supportedConfigIds.toMutableList()
.filter { it in PUBLIC_AVAILABLE_CONFIG_IDS }.toSet(),
it.supportedSlotDurations.toSet(),
it.supportedRangingUpdateRates.toSet(),
it.supportsRangingIntervalReconfigure,
it.hasBackgroundRangingSupport)
}
return if (isController) {
val uwbComplexChannel = uwbClient.complexChannel
UwbControllerSessionScopeAospImpl(
uwbClient,
rangingCapabilities!!,
localAddress!!,
UwbComplexChannel(uwbComplexChannel!!.channel, uwbComplexChannel.preambleIndex)
)
} else {
UwbControleeSessionScopeAospImpl(
uwbClient,
rangingCapabilities!!,
localAddress!!
)
}
} catch (e: Exception) {
throw e
}
}
}