feat: show scan results for Kirby devices in list
This commit is contained in:
@@ -46,4 +46,5 @@ dependencies {
|
|||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
package com.example.sensortestingapp
|
package com.example.sensortestingapp
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import org.junit.Assert.*
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instrumented test, which will execute on an Android device.
|
* Instrumented test, which will execute on an Android device.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -22,9 +22,11 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||||
import com.example.sensortestingapp.databinding.ActivityMainBinding
|
import com.example.sensortestingapp.databinding.ActivityMainBinding
|
||||||
import java.util.stream.Collectors
|
|
||||||
import java.util.stream.Collectors.toList
|
|
||||||
|
|
||||||
private const val ENABLE_BLUETOOTH_REQUEST_CODE = 1
|
private const val ENABLE_BLUETOOTH_REQUEST_CODE = 1
|
||||||
|
|
||||||
@@ -70,7 +72,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var scanResults = HashMap<String, ScanResult>();
|
private var kirbyScanResults = ArrayList<ScanResult>();
|
||||||
|
|
||||||
|
private val scanResultAdapter: ScanResultAdapter by lazy {
|
||||||
|
ScanResultAdapter(kirbyScanResults) {
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
@@ -82,6 +90,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
setSupportActionBar(binding.toolbar)
|
setSupportActionBar(binding.toolbar)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
binding.fab.setOnClickListener { view ->
|
binding.fab.setOnClickListener { view ->
|
||||||
if (!isScanning) {
|
if (!isScanning) {
|
||||||
startBleScan()
|
startBleScan()
|
||||||
@@ -92,6 +101,26 @@ class MainActivity : AppCompatActivity() {
|
|||||||
// .setAnchorView(R.id.fab)
|
// .setAnchorView(R.id.fab)
|
||||||
// .setAction("Action", null).show()
|
// .setAction("Action", null).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupRecyclerView()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRecyclerView() {
|
||||||
|
binding.scanResultsRecyclerView.apply {
|
||||||
|
adapter = scanResultAdapter
|
||||||
|
layoutManager = LinearLayoutManager(
|
||||||
|
this@MainActivity,
|
||||||
|
RecyclerView.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
isNestedScrollingEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
val animator = binding.scanResultsRecyclerView.itemAnimator
|
||||||
|
if (animator is SimpleItemAnimator) {
|
||||||
|
animator.supportsChangeAnimations = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
@@ -178,18 +207,25 @@ class MainActivity : AppCompatActivity() {
|
|||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
val scanCallback = object : ScanCallback() {
|
val scanCallback = object : ScanCallback() {
|
||||||
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
||||||
|
Log.d(
|
||||||
scanResults.merge(result.device.address, result, fun(o, n): ScanResult {
|
|
||||||
return if (n.rssi >= o.rssi) n else o;
|
|
||||||
})
|
|
||||||
val displayScanResults = getDisplayScanResults();
|
|
||||||
Log.i(
|
|
||||||
"ScanCallback",
|
"ScanCallback",
|
||||||
"Found BLE device ${result.device.name ?: result.device.address} (rssi: ${result.rssi}). Current scan results (${displayScanResults.size}): ${
|
"Found BLE device with address ${result.device.address} (name: ${result.device.name}, rssi: ${result.rssi})"
|
||||||
toDebugScanResults(displayScanResults)
|
|
||||||
}"
|
|
||||||
)
|
)
|
||||||
|
if ((result.device.name ?: "").lowercase().contains("kirby")) {
|
||||||
|
Log.i(
|
||||||
|
"ScanCallback",
|
||||||
|
"Found Kirby device with name ${result.device.name} (address: ${result.device.address}, rssi: ${result.rssi})"
|
||||||
|
)
|
||||||
|
val index =
|
||||||
|
kirbyScanResults.indexOfFirst { r -> r.device.address == result.device.address }
|
||||||
|
if (index == -1) {
|
||||||
|
kirbyScanResults.add(result);
|
||||||
|
scanResultAdapter.notifyItemInserted(index)
|
||||||
|
} else {
|
||||||
|
kirbyScanResults[index] = result
|
||||||
|
scanResultAdapter.notifyItemChanged(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScanFailed(errorCode: Int) {
|
override fun onScanFailed(errorCode: Int) {
|
||||||
@@ -197,25 +233,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDisplayScanResults(): List<ScanResult> {
|
|
||||||
return scanResults.values.stream()
|
|
||||||
.filter { r -> r.rssi > -80 }
|
|
||||||
.sorted { r1, r2 -> Integer.compare(r2.rssi, r1.rssi) }
|
|
||||||
.collect(toList())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toDebugScanResults(scanResults: List<ScanResult>): String {
|
|
||||||
return scanResults.stream()
|
|
||||||
.map { r -> "${r.device.name ?: r.device.address} (${r.rssi})" }
|
|
||||||
.collect(Collectors.joining(", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun startBleScan() {
|
private fun startBleScan() {
|
||||||
if (!hasRequiredRuntimePermissions()) {
|
if (!hasRequiredRuntimePermissions()) {
|
||||||
requestRelevantRuntimePermissions()
|
requestRelevantRuntimePermissions()
|
||||||
} else {
|
} else {
|
||||||
scanResults.clear()
|
kirbyScanResults.clear()
|
||||||
|
scanResultAdapter.notifyDataSetChanged()
|
||||||
bleScanner.startScan(null, scanSettings, scanCallback)
|
bleScanner.startScan(null, scanSettings, scanCallback)
|
||||||
isScanning = true
|
isScanning = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.example.sensortestingapp
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.bluetooth.le.ScanResult
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
class ScanResultAdapter(
|
||||||
|
private val items: List<ScanResult>,
|
||||||
|
private val onClickListener: ((device: ScanResult) -> Unit)
|
||||||
|
) : RecyclerView.Adapter<ScanResultAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a reference to the type of views that you are using
|
||||||
|
* (custom ViewHolder)
|
||||||
|
*/
|
||||||
|
class ViewHolder(
|
||||||
|
private val view: View,
|
||||||
|
private val onClickListener: ((device: ScanResult) -> Unit)
|
||||||
|
) : RecyclerView.ViewHolder(view) {
|
||||||
|
|
||||||
|
val deviceNameView: TextView
|
||||||
|
val macAddressView: TextView
|
||||||
|
val signalStrengthView: TextView
|
||||||
|
|
||||||
|
init {
|
||||||
|
deviceNameView = view.findViewById(R.id.device_name)
|
||||||
|
macAddressView = view.findViewById(R.id.mac_address)
|
||||||
|
signalStrengthView = view.findViewById(R.id.signal_strength)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(result: ScanResult) {
|
||||||
|
deviceNameView.text = result.device.name ?: "Unnamed"
|
||||||
|
macAddressView.text = result.device.address
|
||||||
|
signalStrengthView.text = "${result.rssi} dBm"
|
||||||
|
view.setOnClickListener { onClickListener.invoke(result) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
// Create a new view, which defines the UI of the list item
|
||||||
|
val view = LayoutInflater.from(viewGroup.context)
|
||||||
|
.inflate(R.layout.row_scan_result, viewGroup, false)
|
||||||
|
|
||||||
|
return ViewHolder(view, onClickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
// Replace the contents of a view (invoked by the layout manager)
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
|
||||||
|
// Get element from your dataset at this position and replace the
|
||||||
|
// contents of the view with that element
|
||||||
|
val item = items[position]
|
||||||
|
holder.bind(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,8 +17,21 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize" />
|
android:layout_height="?attr/actionBarSize" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/scan_results_recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
tools:listitem="@layout/row_scan_result" />
|
||||||
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/fab"
|
android:id="@+id/fab"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -29,4 +42,5 @@
|
|||||||
android:text='Scan'
|
android:text='Scan'
|
||||||
app:srcCompat="@android:drawable/stat_sys_data_bluetooth" />
|
app:srcCompat="@android:drawable/stat_sys_data_bluetooth" />
|
||||||
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
39
app/src/main/res/layout/row_scan_result.xml
Normal file
39
app/src/main/res/layout/row_scan_result.xml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:background="?android:attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/device_name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:text="Device Name" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/mac_address"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/device_name"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:text="MAC: XX:XX:XX:XX:XX" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/signal_strength"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
tools:text="-100 dBm" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
Reference in New Issue
Block a user