refactor: extract BLE logic from MainActivity to BleRelayManager
This commit is contained in:
parent
0f3b695140
commit
404f46acf8
1 changed files with 62 additions and 339 deletions
|
|
@ -1,193 +1,75 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package com.eddy.atomrelay
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCallback
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothGattDescriptor
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.bluetooth.le.ScanCallback
|
||||
import android.bluetooth.le.ScanFilter
|
||||
import android.bluetooth.le.ScanResult
|
||||
import android.bluetooth.le.ScanSettings
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.ParcelUuid
|
||||
import android.util.Log
|
||||
import android.view.MotionEvent
|
||||
import android.widget.Button
|
||||
import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import java.util.UUID
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
class MainActivity : AppCompatActivity(), BleCallbacks {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AtomRelay"
|
||||
private val SERVICE_UUID: UUID = UUID.fromString("4fafc201-1fb5-459e-8fcc-c5c9c331914b")
|
||||
private val CHARACTERISTIC_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d8")
|
||||
private val UUID_CHAR_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d9")
|
||||
private val CCCD_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
|
||||
private const val DEVICE_NAME = "SylDa_Server"
|
||||
private const val HMAC_PKT_SIZE = 37
|
||||
private const val HMAC_LEN = 32
|
||||
private const val REQUEST_SETTINGS = 1
|
||||
private const val SCAN_TIMEOUT_MS = 30000L
|
||||
private const val RECONNECT_DELAY_MS = 2000L
|
||||
private const val POST_SETTINGS_RESCAN_MS = 500L
|
||||
private const val ON_RESUME_SCAN_DELAY_MS = 300L
|
||||
private const val MAX_SCAN_RETRIES = 3
|
||||
}
|
||||
|
||||
private var bluetoothGatt: BluetoothGatt? = null
|
||||
private var relayCharacteristic: BluetoothGattCharacteristic? = null
|
||||
private var isScanning = false
|
||||
private var scanTimedOut = false
|
||||
private var scanTimeoutRetries = 0
|
||||
private lateinit var manager: BleRelayManager
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val scanRunnable = Runnable { startScan() }
|
||||
private var scanTimeoutRunnable: Runnable? = null
|
||||
|
||||
private var seq = 1
|
||||
private var hmacKey: ByteArray? = null
|
||||
private var deviceUuid: ByteArray? = null
|
||||
private var triedAddresses = mutableSetOf<String>()
|
||||
private var lastTheme: String? = null
|
||||
|
||||
private lateinit var statusText: TextView
|
||||
private lateinit var relay1Button: Button
|
||||
private lateinit var relay2Button: Button
|
||||
private var lastTheme: String? = null
|
||||
|
||||
private val scanCallback = object : ScanCallback() {
|
||||
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
||||
val device = result.device
|
||||
val address = device?.address ?: return
|
||||
val name = device?.name ?: ""
|
||||
Log.d(TAG, "Found device: $name ($address)")
|
||||
if (triedAddresses.contains(address)) {
|
||||
Log.d(TAG, "Skipping already-tried device $address")
|
||||
return
|
||||
override fun onStatusChanged(status: BleStatus) {
|
||||
when (status) {
|
||||
BleStatus.Connected -> {
|
||||
statusText.text = getString(R.string.status_connected)
|
||||
setupRelayButton()
|
||||
setupRelay2Button()
|
||||
relay1Button.isEnabled = true
|
||||
relay2Button.isEnabled = true
|
||||
}
|
||||
stopScan()
|
||||
connectToDevice(device)
|
||||
}
|
||||
}
|
||||
|
||||
private val gattCallback = object : BluetoothGattCallback() {
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
||||
if (status != BluetoothGatt.GATT_SUCCESS) {
|
||||
Log.w(TAG, "GATT connection state change error: status=$status newState=$newState")
|
||||
BleStatus.Disconnected -> {
|
||||
statusText.text = getString(R.string.status_connecting)
|
||||
relay1Button.isEnabled = false
|
||||
relay2Button.isEnabled = false
|
||||
}
|
||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||
if (hmacKey == null) {
|
||||
Log.w(TAG, "GATT connected but no key — disconnecting")
|
||||
runOnUiThread { statusText.text = getString(R.string.status_no_key) }
|
||||
gatt.disconnect()
|
||||
return
|
||||
BleStatus.Scanning -> statusText.text = getString(R.string.status_scanning)
|
||||
BleStatus.Connecting -> statusText.text = getString(R.string.status_connecting)
|
||||
BleStatus.NoKey -> statusText.text = getString(R.string.status_no_key)
|
||||
BleStatus.WrongDevice -> statusText.text = getString(R.string.status_wrong_device)
|
||||
BleStatus.ServiceNotFound -> statusText.text = getString(R.string.status_service_not_found)
|
||||
BleStatus.ServiceError -> statusText.text = getString(R.string.status_service_error)
|
||||
BleStatus.CommError -> statusText.text = getString(R.string.status_comm_error)
|
||||
BleStatus.BluetoothDisabled -> statusText.text = getString(R.string.status_bluetooth_disabled)
|
||||
BleStatus.PermissionsDenied -> {
|
||||
statusText.text = getString(R.string.status_permissions_denied)
|
||||
relay1Button.isEnabled = false
|
||||
relay2Button.isEnabled = false
|
||||
}
|
||||
BleStatus.DeviceNotFound -> {
|
||||
relay1Button.setOnTouchListener(null)
|
||||
relay2Button.setOnTouchListener(null)
|
||||
relay1Button.setOnClickListener(null)
|
||||
relay2Button.setOnClickListener(null)
|
||||
relay1Button.text = getString(R.string.button_retry_scan)
|
||||
relay2Button.text = getString(R.string.button_hold)
|
||||
relay2Button.isEnabled = false
|
||||
relay1Button.isEnabled = true
|
||||
relay1Button.setOnClickListener {
|
||||
setupRelayButton()
|
||||
setupRelay2Button()
|
||||
manager.startScan()
|
||||
}
|
||||
Log.d(TAG, "GATT connected")
|
||||
bluetoothGatt = gatt
|
||||
runOnUiThread { statusText.text = getString(R.string.status_discovering_services) }
|
||||
gatt.discoverServices()
|
||||
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||
Log.d(TAG, "GATT disconnected")
|
||||
runOnUiThread {
|
||||
statusText.text = getString(R.string.status_connecting)
|
||||
relay1Button.isEnabled = false
|
||||
relay2Button.isEnabled = false
|
||||
}
|
||||
gatt.close()
|
||||
bluetoothGatt = null
|
||||
relayCharacteristic = null
|
||||
seq = 1
|
||||
handler.postDelayed(scanRunnable, RECONNECT_DELAY_MS)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
|
||||
if (status != BluetoothGatt.GATT_SUCCESS) {
|
||||
Log.w(TAG, "Service discovery failed (status=$status)")
|
||||
runOnUiThread { statusText.text = getString(R.string.status_service_error) }
|
||||
return
|
||||
}
|
||||
Log.d(TAG, "Services discovered, requesting MTU...")
|
||||
gatt.requestMtu(256)
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
|
||||
if (status != BluetoothGatt.GATT_SUCCESS) {
|
||||
Log.w(TAG, "MTU negotiation failed (status=$status)")
|
||||
}
|
||||
Log.d(TAG, "MTU changed to $mtu")
|
||||
val service = gatt.getService(SERVICE_UUID)
|
||||
if (service != null) {
|
||||
relayCharacteristic = service.getCharacteristic(CHARACTERISTIC_UUID)
|
||||
val uuidChar = service.getCharacteristic(UUID_CHAR_UUID)
|
||||
if (relayCharacteristic != null && uuidChar != null) {
|
||||
Log.d(TAG, "Reading device UUID characteristic...")
|
||||
gatt.readCharacteristic(uuidChar)
|
||||
} else {
|
||||
Log.e(TAG, "Service or characteristics not found")
|
||||
runOnUiThread { statusText.text = getString(R.string.status_service_not_found) }
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Service not found")
|
||||
runOnUiThread { statusText.text = getString(R.string.status_service_not_found) }
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) {
|
||||
if (characteristic.uuid == UUID_CHAR_UUID && status == BluetoothGatt.GATT_SUCCESS) {
|
||||
val storedUuid = deviceUuid
|
||||
if (storedUuid != null && value.contentEquals(storedUuid)) {
|
||||
Log.d(TAG, "UUID verified, setting up relay")
|
||||
relayCharacteristic?.let { rc ->
|
||||
gatt.setCharacteristicNotification(rc, true)
|
||||
val descriptor = rc.getDescriptor(CCCD_UUID)
|
||||
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||
gatt.writeDescriptor(descriptor)
|
||||
}
|
||||
runOnUiThread {
|
||||
setupRelayButton()
|
||||
setupRelay2Button()
|
||||
statusText.text = getString(R.string.status_connected)
|
||||
relay1Button.isEnabled = true
|
||||
relay2Button.isEnabled = true
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "UUID mismatch — disconnecting")
|
||||
triedAddresses.add(gatt.device.address)
|
||||
runOnUiThread { statusText.text = getString(R.string.status_wrong_device) }
|
||||
gatt.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
Log.d(TAG, "Write confirmed by local BLE stack (status=$status)")
|
||||
} else {
|
||||
Log.w(TAG, "Write failed (status=$status)")
|
||||
runOnUiThread { statusText.text = getString(R.string.status_comm_error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -198,6 +80,8 @@ class MainActivity : AppCompatActivity() {
|
|||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
manager = BleRelayManager(this, this)
|
||||
|
||||
statusText = findViewById(R.id.statusText)
|
||||
relay1Button = findViewById(R.id.relay1Button)
|
||||
relay2Button = findViewById(R.id.relay2Button)
|
||||
|
|
@ -214,14 +98,10 @@ class MainActivity : AppCompatActivity() {
|
|||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == REQUEST_SETTINGS && resultCode == SettingsActivity.RESULT_SCAN) {
|
||||
stopScan()
|
||||
bluetoothGatt?.close()
|
||||
bluetoothGatt = null
|
||||
relayCharacteristic = null
|
||||
seq = 1
|
||||
triedAddresses.clear()
|
||||
loadState()
|
||||
handler.postDelayed(scanRunnable, POST_SETTINGS_RESCAN_MS)
|
||||
manager.disconnect()
|
||||
manager.resetTriedAddresses()
|
||||
manager.updateKeys()
|
||||
handler.postDelayed({ manager.startScan() }, POST_SETTINGS_RESCAN_MS)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -233,54 +113,17 @@ class MainActivity : AppCompatActivity() {
|
|||
recreate()
|
||||
return
|
||||
}
|
||||
loadState()
|
||||
if (hmacKey == null || deviceUuid == null) {
|
||||
statusText.text = getString(R.string.status_no_key)
|
||||
} else if (!isScanning && bluetoothGatt == null) {
|
||||
triedAddresses.clear()
|
||||
handler.postDelayed(scanRunnable, ON_RESUME_SCAN_DELAY_MS)
|
||||
if (!manager.hasPermissions()) {
|
||||
requestPermissions()
|
||||
return
|
||||
}
|
||||
manager.resetTriedAddresses()
|
||||
handler.postDelayed({ manager.startScan() }, ON_RESUME_SCAN_DELAY_MS)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
handler.removeCallbacks(scanRunnable)
|
||||
stopScan()
|
||||
}
|
||||
|
||||
private fun loadState() {
|
||||
val prefs = PrefsHelper.getPrefs(this)
|
||||
val hexKey = prefs.getString(PrefsHelper.PREF_KEY, null)
|
||||
hmacKey = hexKey?.let { hexStringToBytes(it) }
|
||||
val hexUuid = prefs.getString(PrefsHelper.PREF_UUID, null)
|
||||
deviceUuid = hexUuid?.let { hexStringToBytes(it) }
|
||||
seq = prefs.getInt(PrefsHelper.PREF_SEQ, 1).coerceAtMost(PrefsHelper.SEQ_MAX)
|
||||
Log.d(TAG, "HMAC key loaded: ${if (hmacKey != null) "${hmacKey!!.size} bytes" else "none"}, UUID: ${if (deviceUuid != null) "${deviceUuid!!.size} bytes" else "none"}, seq=$seq")
|
||||
}
|
||||
|
||||
private fun hexStringToBytes(hex: String): ByteArray? {
|
||||
val cleaned = hex.uppercase().filter { it in "0123456789ABCDEF" }
|
||||
if (cleaned.isEmpty() || cleaned.length % 2 != 0) return null
|
||||
return cleaned.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
}
|
||||
|
||||
private fun hmacSha256(data: ByteArray, key: ByteArray): ByteArray {
|
||||
val mac = Mac.getInstance("HmacSHA256")
|
||||
mac.init(SecretKeySpec(key, "HmacSHA256"))
|
||||
return mac.doFinal(data)
|
||||
}
|
||||
|
||||
private fun buildPacket(cmd: Byte, key: ByteArray): ByteArray {
|
||||
val seqBytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(seq).array()
|
||||
val hmacData = ByteArray(5)
|
||||
hmacData[0] = cmd
|
||||
System.arraycopy(seqBytes, 0, hmacData, 1, 4)
|
||||
val hmac = hmacSha256(hmacData, key)
|
||||
val packet = ByteArray(HMAC_PKT_SIZE)
|
||||
packet[0] = cmd
|
||||
System.arraycopy(seqBytes, 0, packet, 1, 4)
|
||||
System.arraycopy(hmac, 0, packet, 5, HMAC_LEN)
|
||||
return packet
|
||||
manager.stopScan()
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
|
|
@ -288,11 +131,9 @@ class MainActivity : AppCompatActivity() {
|
|||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == 1) {
|
||||
if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
|
||||
startScan()
|
||||
manager.startScan()
|
||||
} else {
|
||||
statusText.text = getString(R.string.status_permissions_denied)
|
||||
relay1Button.isEnabled = false
|
||||
relay2Button.isEnabled = false
|
||||
onStatusChanged(BleStatus.PermissionsDenied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -303,12 +144,12 @@ class MainActivity : AppCompatActivity() {
|
|||
relay1Button.setOnTouchListener { _, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
sendRelayCommand(0x01.toByte())
|
||||
manager.sendRelayCommand(0x01.toByte())
|
||||
relay1Button.text = getString(R.string.button_on)
|
||||
true
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
sendRelayCommand(0x00.toByte())
|
||||
manager.sendRelayCommand(0x00.toByte())
|
||||
relay1Button.text = getString(R.string.button_hold)
|
||||
true
|
||||
}
|
||||
|
|
@ -323,12 +164,12 @@ class MainActivity : AppCompatActivity() {
|
|||
relay2Button.setOnTouchListener { _, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
sendRelayCommand(0x02.toByte())
|
||||
manager.sendRelayCommand(0x02.toByte())
|
||||
relay2Button.text = getString(R.string.button_on)
|
||||
true
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
sendRelayCommand(0x03.toByte())
|
||||
manager.sendRelayCommand(0x03.toByte())
|
||||
relay2Button.text = getString(R.string.button_hold)
|
||||
true
|
||||
}
|
||||
|
|
@ -337,132 +178,14 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun startScan() {
|
||||
if (isScanning) return
|
||||
if (!hasPermissions()) {
|
||||
requestPermissions()
|
||||
return
|
||||
}
|
||||
|
||||
val adapter = BluetoothAdapter.getDefaultAdapter()
|
||||
if (adapter == null || !adapter.isEnabled) {
|
||||
statusText.text = getString(R.string.status_bluetooth_disabled)
|
||||
return
|
||||
}
|
||||
|
||||
if (hmacKey == null || deviceUuid == null) {
|
||||
statusText.text = getString(R.string.status_no_key)
|
||||
return
|
||||
}
|
||||
|
||||
isScanning = true
|
||||
scanTimedOut = false
|
||||
statusText.text = getString(R.string.status_scanning)
|
||||
Log.d(TAG, "Starting BLE scan with service UUID filter")
|
||||
|
||||
val filter = ScanFilter.Builder().setServiceUuid(ParcelUuid(SERVICE_UUID)).build()
|
||||
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
|
||||
adapter.bluetoothLeScanner.startScan(listOf(filter), settings, scanCallback)
|
||||
|
||||
scanTimeoutRunnable?.let { handler.removeCallbacks(it) }
|
||||
scanTimeoutRunnable = Runnable {
|
||||
if (isScanning) {
|
||||
scanTimedOut = true
|
||||
stopScan()
|
||||
}
|
||||
}
|
||||
handler.postDelayed(scanTimeoutRunnable!!, SCAN_TIMEOUT_MS)
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun stopScan() {
|
||||
if (!isScanning) return
|
||||
isScanning = false
|
||||
scanTimeoutRunnable?.let { handler.removeCallbacks(it) }
|
||||
val adapter = BluetoothAdapter.getDefaultAdapter()
|
||||
adapter?.bluetoothLeScanner?.stopScan(scanCallback)
|
||||
if (scanTimedOut) {
|
||||
if (scanTimeoutRetries < MAX_SCAN_RETRIES) {
|
||||
scanTimeoutRetries++
|
||||
Log.d(TAG, "Scan timeout, retry $scanTimeoutRetries/$MAX_SCAN_RETRIES")
|
||||
statusText.text = getString(R.string.status_scanning)
|
||||
handler.postDelayed(scanRunnable, 2000L)
|
||||
return
|
||||
}
|
||||
statusText.text = getString(R.string.status_device_not_found)
|
||||
relay1Button.setOnTouchListener(null)
|
||||
relay2Button.setOnTouchListener(null)
|
||||
relay1Button.setOnClickListener(null)
|
||||
relay2Button.setOnClickListener(null)
|
||||
relay1Button.text = getString(R.string.button_retry_scan)
|
||||
relay2Button.text = getString(R.string.button_hold)
|
||||
relay2Button.isEnabled = false
|
||||
relay1Button.isEnabled = true
|
||||
relay1Button.setOnClickListener {
|
||||
setupRelayButton()
|
||||
setupRelay2Button()
|
||||
startScan()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun connectToDevice(device: android.bluetooth.BluetoothDevice) {
|
||||
runOnUiThread { statusText.text = getString(R.string.status_connecting) }
|
||||
Log.d(TAG, "Connecting to ${device.address}")
|
||||
bluetoothGatt = device.connectGatt(this, false, gattCallback)
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun sendRelayCommand(cmd: Byte) {
|
||||
val char = relayCharacteristic ?: return
|
||||
val gatt = bluetoothGatt ?: return
|
||||
val key = hmacKey ?: return
|
||||
|
||||
val packet = buildPacket(cmd, key)
|
||||
char.value = packet
|
||||
// WRITE_TYPE_NO_RESPONSE is intentional. Previously, WRITE_TYPE_DEFAULT (with ACK) was
|
||||
// used, but the BLE write acknowledgement took long enough that the user could release
|
||||
// the button before the ACK arrived. When ACTION_UP fired during a pending write, the
|
||||
// release was silently ignored and the relay (and app) got stuck in the ON state.
|
||||
// WRITE_TYPE_NO_RESPONSE removes that latency and allows immediate ON→OFF sequences.
|
||||
// This means the UI reflects the user's intent ("pressed" / "released") rather than
|
||||
// confirmed relay state — an acceptable trade-off for a hold-to-activate interaction.
|
||||
char.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
||||
gatt.writeCharacteristic(char)
|
||||
seq++
|
||||
if (seq > PrefsHelper.SEQ_MAX) seq = 1
|
||||
PrefsHelper.getPrefs(this).edit().putInt(PrefsHelper.PREF_SEQ, seq).apply()
|
||||
Log.d(TAG, "Sent relay cmd 0x${cmd.toString(16)} (seq=${seq - 1})")
|
||||
}
|
||||
|
||||
private fun getRequiredPermissions(): Array<String> {
|
||||
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
||||
arrayOf(
|
||||
Manifest.permission.BLUETOOTH_SCAN,
|
||||
Manifest.permission.BLUETOOTH_CONNECT,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION
|
||||
)
|
||||
} else {
|
||||
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasPermissions(): Boolean {
|
||||
return getRequiredPermissions().all {
|
||||
ActivityCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestPermissions() {
|
||||
ActivityCompat.requestPermissions(this, getRequiredPermissions(), 1)
|
||||
val perms = manager.getRequiredPermissions()
|
||||
ActivityCompat.requestPermissions(this, perms, 1)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
stopScan()
|
||||
bluetoothGatt?.close()
|
||||
manager.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue