Change scanner to CompanionDeviceManager

This commit is contained in:
Sylwester Zieliński
2021-09-23 15:25:28 +02:00
parent c944a446ef
commit 91b0e39f8e
9 changed files with 80 additions and 95 deletions

View File

@@ -1,15 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="no.nordicsemi.android.scanner">
<uses-feature android:name="android.software.companion_device_setup"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
tools:ignore="CoarseFineLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest>

View File

@@ -1,71 +0,0 @@
package no.nordicsemi.android.scanner
import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.events.exhaustive
@Composable
internal fun ListOfDevicesScreen(onDeviceSelected: (BluetoothDevice) -> Unit) {
val viewModel = hiltViewModel<NordicBleScannerViewModel>()
val result = viewModel.scannerResult.collectAsState().value
when (result) {
is DeviceListResult -> DeviceListView(result.devices, onDeviceSelected)
is ScanningErrorResult -> ScanningErrorView()
}.exhaustive
}
@SuppressLint("MissingPermission")
@Composable
private fun DeviceListView(
devices: List<BluetoothDevice>,
onDeviceSelected: (BluetoothDevice) -> Unit
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Spacer(modifier = Modifier.height(16.dp))
Text(stringResource(id = R.string.scanner__list_of_devices))
Spacer(modifier = Modifier.height(16.dp))
LazyColumn(
modifier = Modifier.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
itemsIndexed(devices) { _, device ->
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { onDeviceSelected(device) }
) {
Column {
Text(device.name ?: stringResource(id = R.string.scanner__no_name))
Spacer(modifier = Modifier.height(8.dp))
Text(text = device.address)
}
}
}
}
}
}
@Composable
private fun ScanningErrorView() {
Text(text = stringResource(id = R.string.scanner__error))
}

View File

@@ -9,9 +9,13 @@ import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import no.nordicsemi.android.events.exhaustive
import no.nordicsemi.android.scanner.bluetooth.BluetoothNotAvailableScreen
import no.nordicsemi.android.scanner.bluetooth.BluetoothNotEnabledScreen
import no.nordicsemi.android.scanner.permissions.RequestPermissionScreen
import no.nordicsemi.android.scanner.tools.ScannerStatus
import no.nordicsemi.android.scanner.ui.BluetoothNotAvailableScreen
import no.nordicsemi.android.scanner.ui.BluetoothNotEnabledScreen
import no.nordicsemi.android.scanner.ui.NordicBleScannerViewModel
import no.nordicsemi.android.scanner.ui.RequestPermissionScreen
import no.nordicsemi.android.scanner.ui.ScanDeviceScreen
import no.nordicsemi.android.scanner.ui.ScannerViewEvent
@Composable
fun ScannerRoute(navController: NavController) {
@@ -35,15 +39,6 @@ private fun ScannerScreen(
ScannerStatus.PERMISSION_REQUIRED -> RequestPermissionScreen { onEvent(ScannerViewEvent.PERMISSION_CHECKED) }
ScannerStatus.NOT_AVAILABLE -> BluetoothNotAvailableScreen()
ScannerStatus.DISABLED -> BluetoothNotEnabledScreen { onEvent(ScannerViewEvent.BLUETOOTH_ENABLED) }
ScannerStatus.ENABLED -> {
onEvent(ScannerViewEvent.ENABLE_SCANNING)
ListOfDevicesScreen {
navController.previousBackStackEntry
?.savedStateHandle
?.set("result", it)
navController.popBackStack()
onEvent(ScannerViewEvent.DISABLE_SCANNING)
}
}
ScannerStatus.ENABLED -> ScanDeviceScreen(navController)
}.exhaustive
}

View File

@@ -1,4 +1,4 @@
package no.nordicsemi.android.scanner
package no.nordicsemi.android.scanner.tools
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter

View File

@@ -1,4 +1,4 @@
package no.nordicsemi.android.scanner
package no.nordicsemi.android.scanner.tools
enum class ScannerStatus {
PERMISSION_REQUIRED, ENABLED, DISABLED, NOT_AVAILABLE

View File

@@ -1,4 +1,4 @@
package no.nordicsemi.android.scanner.bluetooth
package no.nordicsemi.android.scanner.ui
import android.app.Activity
import android.bluetooth.BluetoothAdapter

View File

@@ -1,9 +1,11 @@
package no.nordicsemi.android.scanner
package no.nordicsemi.android.scanner.ui
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import no.nordicsemi.android.events.exhaustive
import no.nordicsemi.android.scanner.tools.NordicBleScanner
import no.nordicsemi.android.scanner.tools.ScannerStatus
import javax.inject.Inject
@HiltViewModel
@@ -20,8 +22,6 @@ internal class NordicBleScannerViewModel @Inject constructor(
when (event) {
ScannerViewEvent.PERMISSION_CHECKED -> onPermissionChecked()
ScannerViewEvent.BLUETOOTH_ENABLED -> onBluetoothEnabled()
ScannerViewEvent.ENABLE_SCANNING -> bleScanner.startScanning()
ScannerViewEvent.DISABLE_SCANNING -> bleScanner.stopScanning()
}.exhaustive
}
@@ -36,7 +36,7 @@ internal class NordicBleScannerViewModel @Inject constructor(
}
enum class ScannerViewEvent {
PERMISSION_CHECKED, BLUETOOTH_ENABLED, ENABLE_SCANNING, DISABLE_SCANNING
PERMISSION_CHECKED, BLUETOOTH_ENABLED
}
internal data class NordicBleScannerState(

View File

@@ -1,4 +1,4 @@
package no.nordicsemi.android.scanner.permissions
package no.nordicsemi.android.scanner.ui
import android.content.Context
import android.content.Intent

View File

@@ -0,0 +1,62 @@
package no.nordicsemi.android.scanner.ui
import android.app.Activity
import android.bluetooth.BluetoothDevice
import android.companion.AssociationRequest
import android.companion.BluetoothDeviceFilter
import android.companion.CompanionDeviceManager
import android.content.Context
import android.content.IntentSender
import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController
@Composable
fun ScanDeviceScreen(navController: NavController,) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
.build()
val pairingRequest: AssociationRequest = AssociationRequest.Builder()
.build()
val deviceManager =
LocalContext.current.getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager
val contract = ActivityResultContracts.StartIntentSenderForResult()
val launcher = rememberLauncherForActivityResult(contract = contract, onResult = {
if (it.resultCode == Activity.RESULT_OK) {
val deviceToPair: BluetoothDevice? = it.data?.getParcelableExtra(
CompanionDeviceManager.EXTRA_DEVICE)
navController.previousBackStackEntry
?.savedStateHandle
?.set("result", deviceToPair)
navController.popBackStack()
}
})
val hasBeenInvoked = remember { mutableStateOf(false) }
if (hasBeenInvoked.value) {
return
}
hasBeenInvoked.value = true
deviceManager.associate(pairingRequest,
object : CompanionDeviceManager.Callback() {
override fun onDeviceFound(chooserLauncher: IntentSender) {
val request = IntentSenderRequest.Builder(chooserLauncher).build()
launcher.launch(request)
}
override fun onFailure(error: CharSequence?) {
}
}, null)
} else {
TODO("VERSION.SDK_INT < O")
}
}