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"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="no.nordicsemi.android.scanner"> 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-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <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" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest> </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.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import no.nordicsemi.android.events.exhaustive import no.nordicsemi.android.events.exhaustive
import no.nordicsemi.android.scanner.bluetooth.BluetoothNotAvailableScreen import no.nordicsemi.android.scanner.tools.ScannerStatus
import no.nordicsemi.android.scanner.bluetooth.BluetoothNotEnabledScreen import no.nordicsemi.android.scanner.ui.BluetoothNotAvailableScreen
import no.nordicsemi.android.scanner.permissions.RequestPermissionScreen 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 @Composable
fun ScannerRoute(navController: NavController) { fun ScannerRoute(navController: NavController) {
@@ -35,15 +39,6 @@ private fun ScannerScreen(
ScannerStatus.PERMISSION_REQUIRED -> RequestPermissionScreen { onEvent(ScannerViewEvent.PERMISSION_CHECKED) } ScannerStatus.PERMISSION_REQUIRED -> RequestPermissionScreen { onEvent(ScannerViewEvent.PERMISSION_CHECKED) }
ScannerStatus.NOT_AVAILABLE -> BluetoothNotAvailableScreen() ScannerStatus.NOT_AVAILABLE -> BluetoothNotAvailableScreen()
ScannerStatus.DISABLED -> BluetoothNotEnabledScreen { onEvent(ScannerViewEvent.BLUETOOTH_ENABLED) } ScannerStatus.DISABLED -> BluetoothNotEnabledScreen { onEvent(ScannerViewEvent.BLUETOOTH_ENABLED) }
ScannerStatus.ENABLED -> { ScannerStatus.ENABLED -> ScanDeviceScreen(navController)
onEvent(ScannerViewEvent.ENABLE_SCANNING)
ListOfDevicesScreen {
navController.previousBackStackEntry
?.savedStateHandle
?.set("result", it)
navController.popBackStack()
onEvent(ScannerViewEvent.DISABLE_SCANNING)
}
}
}.exhaustive }.exhaustive
} }

View File

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

View File

@@ -1,4 +1,4 @@
package no.nordicsemi.android.scanner package no.nordicsemi.android.scanner.tools
enum class ScannerStatus { enum class ScannerStatus {
PERMISSION_REQUIRED, ENABLED, DISABLED, NOT_AVAILABLE 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.app.Activity
import android.bluetooth.BluetoothAdapter 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 androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import no.nordicsemi.android.events.exhaustive import no.nordicsemi.android.events.exhaustive
import no.nordicsemi.android.scanner.tools.NordicBleScanner
import no.nordicsemi.android.scanner.tools.ScannerStatus
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@@ -20,8 +22,6 @@ internal class NordicBleScannerViewModel @Inject constructor(
when (event) { when (event) {
ScannerViewEvent.PERMISSION_CHECKED -> onPermissionChecked() ScannerViewEvent.PERMISSION_CHECKED -> onPermissionChecked()
ScannerViewEvent.BLUETOOTH_ENABLED -> onBluetoothEnabled() ScannerViewEvent.BLUETOOTH_ENABLED -> onBluetoothEnabled()
ScannerViewEvent.ENABLE_SCANNING -> bleScanner.startScanning()
ScannerViewEvent.DISABLE_SCANNING -> bleScanner.stopScanning()
}.exhaustive }.exhaustive
} }
@@ -36,7 +36,7 @@ internal class NordicBleScannerViewModel @Inject constructor(
} }
enum class ScannerViewEvent { enum class ScannerViewEvent {
PERMISSION_CHECKED, BLUETOOTH_ENABLED, ENABLE_SCANNING, DISABLE_SCANNING PERMISSION_CHECKED, BLUETOOTH_ENABLED
} }
internal data class NordicBleScannerState( 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.Context
import android.content.Intent 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")
}
}