mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-16 11:16:12 -04:00
Add option to show fake devices
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
package eu.darken.capod.common.bluetooth
|
||||||
|
|
||||||
|
import android.bluetooth.le.ScanResult
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.core.util.forEach
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class BleScanResult(
|
||||||
|
val address: String,
|
||||||
|
val rssi: Int,
|
||||||
|
val generatedAtNanos: Long,
|
||||||
|
val manufacturerSpecificData: Map<Int, ByteArray>
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
fun getManufacturerSpecificData(id: Int): ByteArray? = manufacturerSpecificData[id]
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
manufacturerSpecificData.forEach { (key, value) ->
|
||||||
|
sb.append("$key: ${value.joinToString(separator = " ") { String.format("%02X", it) }}")
|
||||||
|
}
|
||||||
|
return "BleScanResult($rssi, $address, $generatedAtNanos, $sb"
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromScanResult(scanResult: ScanResult) = BleScanResult(
|
||||||
|
address = scanResult.device.address,
|
||||||
|
rssi = scanResult.rssi,
|
||||||
|
generatedAtNanos = scanResult.timestampNanos,
|
||||||
|
manufacturerSpecificData = mutableMapOf<Int, ByteArray>().apply {
|
||||||
|
scanResult.scanRecord?.manufacturerSpecificData?.forEach { key, value ->
|
||||||
|
this[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ import android.bluetooth.le.ScanResult
|
|||||||
import android.bluetooth.le.ScanSettings
|
import android.bluetooth.le.ScanSettings
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import eu.darken.capod.common.SystemClockWrap
|
||||||
|
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
@@ -13,6 +15,7 @@ import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
|||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -20,24 +23,25 @@ import javax.inject.Singleton
|
|||||||
class BleScanner @Inject constructor(
|
class BleScanner @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val bluetoothManager: BluetoothManager2,
|
private val bluetoothManager: BluetoothManager2,
|
||||||
|
private val debugSettings: DebugSettings,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun scan(
|
fun scan(
|
||||||
filter: Set<ScanFilter> = ProximityPairing.getBleScanFilter(),
|
filter: Set<ScanFilter> = ProximityPairing.getBleScanFilter(),
|
||||||
mode: Int = ScanSettings.SCAN_MODE_BALANCED,
|
mode: Int = ScanSettings.SCAN_MODE_BALANCED,
|
||||||
): Flow<List<ScanResult>> = callbackFlow {
|
): Flow<List<BleScanResult>> = callbackFlow {
|
||||||
val adapter = bluetoothManager.adapter
|
val adapter = bluetoothManager.adapter
|
||||||
val scanner = bluetoothManager.scanner
|
val scanner = bluetoothManager.scanner
|
||||||
|
|
||||||
val callback = object : ScanCallback() {
|
val callback = object : ScanCallback() {
|
||||||
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
||||||
log(TAG, VERBOSE) { "onScanResult(callbackType=$callbackType, result=$result)" }
|
log(TAG, VERBOSE) { "onScanResult(callbackType=$callbackType, result=$result)" }
|
||||||
trySend(listOf(result))
|
trySend(listOf(BleScanResult.fromScanResult(result)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBatchScanResults(results: MutableList<ScanResult>) {
|
override fun onBatchScanResults(results: MutableList<ScanResult>) {
|
||||||
log(TAG, VERBOSE) { "onBatchScanResults(results=$results)" }
|
log(TAG, VERBOSE) { "onBatchScanResults(results=$results)" }
|
||||||
trySend(results)
|
trySend(results.map { BleScanResult.fromScanResult(it) })
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScanFailed(errorCode: Int) {
|
override fun onScanFailed(errorCode: Int) {
|
||||||
@@ -68,7 +72,58 @@ class BleScanner @Inject constructor(
|
|||||||
scanner.stopScan(callback)
|
scanner.stopScan(callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.map { origs ->
|
||||||
|
val fakeDevices = mutableListOf<BleScanResult>()
|
||||||
|
if (debugSettings.showFakeData.value) {
|
||||||
|
// AirPods Pro
|
||||||
|
BleScanResult(
|
||||||
|
address = "78:73:AF:B4:85:5E",
|
||||||
|
rssi = -48,
|
||||||
|
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
|
||||||
|
manufacturerSpecificData = mapOf(76 to "07 19 01 0E 20 75 AA B6 31 00 00 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".hexToByteArray())
|
||||||
|
).run { fakeDevices.add(this) }
|
||||||
|
// AirPods Gen1
|
||||||
|
BleScanResult(
|
||||||
|
address = "4E:9E:D1:49:D2:6D",
|
||||||
|
rssi = -55,
|
||||||
|
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 200,
|
||||||
|
manufacturerSpecificData = mapOf(76 to "07 19 01 02 20 55 AF 56 31 00 00 6F E4 DF 10 AF 10 60 81 03 3B 76 D9 C7 11 22 88".hexToByteArray())
|
||||||
|
).run { fakeDevices.add(this) }
|
||||||
|
// AirPods Max
|
||||||
|
BleScanResult(
|
||||||
|
address = "7E:E5:C7:65:D2:B5",
|
||||||
|
rssi = -57,
|
||||||
|
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 300,
|
||||||
|
manufacturerSpecificData = mapOf(76 to "07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E".hexToByteArray())
|
||||||
|
).run { fakeDevices.add(this) }
|
||||||
|
// BeatsFlex
|
||||||
|
BleScanResult(
|
||||||
|
address = "5E:9E:D1:49:D2:6D",
|
||||||
|
rssi = -59,
|
||||||
|
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
|
||||||
|
manufacturerSpecificData = mapOf(76 to "07 19 01 10 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".hexToByteArray())
|
||||||
|
).run { fakeDevices.add(this) }
|
||||||
|
// Unknown Device
|
||||||
|
BleScanResult(
|
||||||
|
address = "6E:9E:D1:49:D2:6D",
|
||||||
|
rssi = -60,
|
||||||
|
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 500,
|
||||||
|
manufacturerSpecificData = mapOf(76 to "07 19 01 FF 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".hexToByteArray())
|
||||||
|
).run { fakeDevices.add(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
origs + fakeDevices
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun String.hexToByteArray(): ByteArray {
|
||||||
|
val trimmed = this
|
||||||
|
.replace(" ", "")
|
||||||
|
.replace(">", "")
|
||||||
|
.replace("<", "")
|
||||||
|
require(trimmed.length % 2 == 0) { "Not a HEX string" }
|
||||||
|
return trimmed.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = logTag("Bluetooth", "BleScanner")
|
private val TAG = logTag("Bluetooth", "BleScanner")
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package eu.darken.capod.common.debug.autoreport
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import androidx.preference.PreferenceDataStore
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import eu.darken.androidstarter.common.preferences.Settings
|
||||||
|
import eu.darken.capod.common.preferences.PreferenceStoreMapper
|
||||||
import eu.darken.capod.common.preferences.createFlowPreference
|
import eu.darken.capod.common.preferences.createFlowPreference
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -10,12 +13,19 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class DebugSettings @Inject constructor(
|
class DebugSettings @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
) {
|
) : Settings() {
|
||||||
|
|
||||||
private val preferences: SharedPreferences = context.getSharedPreferences("settings_debug", Context.MODE_PRIVATE)
|
override val preferences: SharedPreferences = context.getSharedPreferences("settings_debug", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
val isAutoReportEnabled = preferences.createFlowPreference("debug.bugreport.automatic.enabled", true)
|
val isAutoReportEnabled = preferences.createFlowPreference("debug.bugreport.automatic.enabled", true)
|
||||||
|
|
||||||
val isDebugModeEnabled = preferences.createFlowPreference("debug.mode.enabled", false)
|
val isDebugModeEnabled = preferences.createFlowPreference("debug.mode.enabled", false)
|
||||||
|
|
||||||
|
val showFakeData = preferences.createFlowPreference("debug.fakedata.enabled", false)
|
||||||
|
|
||||||
|
override val preferenceDataStore: PreferenceDataStore = PreferenceStoreMapper(
|
||||||
|
isDebugModeEnabled,
|
||||||
|
showFakeData,
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,6 @@ import com.squareup.moshi.Moshi
|
|||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import eu.darken.androidstarter.common.preferences.Settings
|
import eu.darken.androidstarter.common.preferences.Settings
|
||||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
|
||||||
import eu.darken.capod.common.preferences.PreferenceStoreMapper
|
import eu.darken.capod.common.preferences.PreferenceStoreMapper
|
||||||
import eu.darken.capod.common.preferences.createFlowPreference
|
import eu.darken.capod.common.preferences.createFlowPreference
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -55,11 +54,6 @@ class GeneralSettings @Inject constructor(
|
|||||||
autoPause,
|
autoPause,
|
||||||
autoPlay,
|
autoPlay,
|
||||||
showAll,
|
showAll,
|
||||||
debugSettings.isDebugModeEnabled,
|
debugSettings.isAutoReportEnabled,
|
||||||
debugSettings.isAutoReportEnabled
|
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
|
||||||
internal val TAG = logTag("Core", "Settings")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -45,10 +45,6 @@ class OverviewFragment : Fragment3(R.layout.main_fragment) {
|
|||||||
subtitle = BuildConfigWrap.VERSION_DESCRIPTION
|
subtitle = BuildConfigWrap.VERSION_DESCRIPTION
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener {
|
||||||
when (it.itemId) {
|
when (it.itemId) {
|
||||||
R.id.menu_item_debuglog -> {
|
|
||||||
vm.toggleDebugLog()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
R.id.menu_item_settings -> {
|
R.id.menu_item_settings -> {
|
||||||
vm.goToSettings()
|
vm.goToSettings()
|
||||||
true
|
true
|
||||||
|
|||||||
+2
@@ -32,6 +32,8 @@ class UnknownPodDeviceCardVH(parent: ViewGroup) :
|
|||||||
RelativeDateTimeFormatter.RelativeUnit.SECONDS
|
RelativeDateTimeFormatter.RelativeUnit.SECONDS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
reception.text = device.getSignalQuality(context)
|
||||||
|
|
||||||
rawdata.text = device.rawDataHex
|
rawdata.text = device.rawDataHex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
package eu.darken.capod.main.ui.settings.general.debug
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
||||||
|
import eu.darken.capod.common.observe2
|
||||||
|
import eu.darken.capod.common.uix.PreferenceFragment2
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class DebugSettingsFragment : PreferenceFragment2() {
|
||||||
|
|
||||||
|
private val vm: DebugSettingsFragmentVM by viewModels()
|
||||||
|
|
||||||
|
@Inject lateinit var debugSettings: DebugSettings
|
||||||
|
|
||||||
|
override val settings: DebugSettings
|
||||||
|
get() = debugSettings
|
||||||
|
|
||||||
|
override val preferenceFile: Int = R.xml.preferences_debug
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
val logPref = findPreference<Preference>("debug.log.record")!!
|
||||||
|
vm.state.observe2(this) {
|
||||||
|
logPref.summary = it.currentLogPath?.path
|
||||||
|
}
|
||||||
|
logPref.setOnPreferenceClickListener {
|
||||||
|
vm.toggleRecorder()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
package eu.darken.capod.main.ui.settings.general.debug
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||||
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
|
import eu.darken.capod.common.debug.recording.core.RecorderModule
|
||||||
|
import eu.darken.capod.common.uix.ViewModel3
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class DebugSettingsFragmentVM @Inject constructor(
|
||||||
|
private val handle: SavedStateHandle,
|
||||||
|
dispatcherProvider: DispatcherProvider,
|
||||||
|
private val recorderModule: RecorderModule,
|
||||||
|
) : ViewModel3(dispatcherProvider) {
|
||||||
|
|
||||||
|
val state = recorderModule.state.asLiveData2()
|
||||||
|
|
||||||
|
fun toggleRecorder() = launch {
|
||||||
|
if (recorderModule.state.first().isRecording) {
|
||||||
|
recorderModule.stopRecorder()
|
||||||
|
} else {
|
||||||
|
recorderModule.startRecorder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = logTag("Settings", "Debug", "VM")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package eu.darken.capod.pods.core
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@@ -14,7 +14,7 @@ interface PodDevice {
|
|||||||
|
|
||||||
val lastSeenAt: Instant
|
val lastSeenAt: Instant
|
||||||
|
|
||||||
val scanResult: ScanResult
|
val scanResult: BleScanResult
|
||||||
|
|
||||||
val rssi: Int
|
val rssi: Int
|
||||||
get() = scanResult.rssi
|
get() = scanResult.rssi
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package eu.darken.capod.pods.core
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import dagger.Reusable
|
import dagger.Reusable
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
@@ -14,7 +14,7 @@ class PodFactory @Inject constructor(
|
|||||||
private val appleFactory: AppleFactory
|
private val appleFactory: AppleFactory
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun createPod(scanResult: ScanResult): PodDevice? {
|
suspend fun createPod(scanResult: BleScanResult): PodDevice? {
|
||||||
log(TAG, VERBOSE) { "Trying to create Pod for $scanResult" }
|
log(TAG, VERBOSE) { "Trying to create Pod for $scanResult" }
|
||||||
|
|
||||||
val pod = appleFactory.create(scanResult)
|
val pod = appleFactory.create(scanResult)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package eu.darken.capod.pods.core.apple
|
package eu.darken.capod.pods.core.apple
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import eu.darken.capod.common.SystemClockWrap
|
import eu.darken.capod.common.SystemClockWrap
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.common.debug.Bugs
|
import eu.darken.capod.common.debug.Bugs
|
||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
||||||
import eu.darken.capod.common.debug.logging.asLog
|
import eu.darken.capod.common.debug.logging.asLog
|
||||||
@@ -28,17 +28,17 @@ class AppleFactory @Inject constructor(
|
|||||||
|
|
||||||
data class KnownDevice(
|
data class KnownDevice(
|
||||||
val identifier: PodDevice.Id,
|
val identifier: PodDevice.Id,
|
||||||
val scanResult: ScanResult,
|
val scanResult: BleScanResult,
|
||||||
val message: ProximityPairing.Message,
|
val message: ProximityPairing.Message,
|
||||||
) {
|
) {
|
||||||
val address: String
|
val address: String
|
||||||
get() = scanResult.device.address
|
get() = scanResult.address
|
||||||
|
|
||||||
val rssi: Int
|
val rssi: Int
|
||||||
get() = scanResult.rssi
|
get() = scanResult.rssi
|
||||||
|
|
||||||
val timestampNanos: Duration
|
val timestampNanos: Duration
|
||||||
get() = Duration.ofNanos(scanResult.timestampNanos)
|
get() = Duration.ofNanos(scanResult.generatedAtNanos)
|
||||||
|
|
||||||
fun isOlderThan(age: Duration): Boolean {
|
fun isOlderThan(age: Duration): Boolean {
|
||||||
val now = Duration.ofNanos(SystemClockWrap.elapsedRealtimeNanos)
|
val now = Duration.ofNanos(SystemClockWrap.elapsedRealtimeNanos)
|
||||||
@@ -55,7 +55,7 @@ class AppleFactory @Inject constructor(
|
|||||||
|
|
||||||
private val cachedValues = mutableMapOf<PodDevice.Id, ValueCache>()
|
private val cachedValues = mutableMapOf<PodDevice.Id, ValueCache>()
|
||||||
|
|
||||||
private suspend fun getMessage(scanResult: ScanResult): ProximityPairing.Message? {
|
private suspend fun getMessage(scanResult: BleScanResult): ProximityPairing.Message? {
|
||||||
val messages = try {
|
val messages = try {
|
||||||
continuityProtocolDecoder.decode(scanResult)
|
continuityProtocolDecoder.decode(scanResult)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -80,8 +80,8 @@ class AppleFactory @Inject constructor(
|
|||||||
return proximityMessage
|
return proximityMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun recognizeDevice(scanResult: ScanResult, message: ProximityPairing.Message): PodDevice.Id {
|
private suspend fun recognizeDevice(scanResult: BleScanResult, message: ProximityPairing.Message): PodDevice.Id {
|
||||||
val address = scanResult.device.address
|
val address = scanResult.address
|
||||||
|
|
||||||
var identifier: PodDevice.Id? = null
|
var identifier: PodDevice.Id? = null
|
||||||
|
|
||||||
@@ -147,26 +147,18 @@ class AppleFactory @Inject constructor(
|
|||||||
return identifier!!
|
return identifier!!
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun create(scanResult: ScanResult): PodDevice? = lock.withLock {
|
suspend fun create(scanResult: BleScanResult): PodDevice? = lock.withLock {
|
||||||
val pm = getMessage(scanResult) ?: return@withLock null
|
val pm = getMessage(scanResult) ?: return@withLock null
|
||||||
|
|
||||||
val identifier = recognizeDevice(scanResult, pm)
|
val identifier = recognizeDevice(scanResult, pm)
|
||||||
|
|
||||||
log(TAG, INFO) {
|
log(TAG, INFO) { "Decoding $scanResult" }
|
||||||
val data = scanResult.scanRecord!!.getManufacturerSpecificData(
|
|
||||||
ContinuityProtocol.APPLE_COMPANY_IDENTIFIER
|
|
||||||
)!!
|
|
||||||
val dataHex = data.joinToString(separator = " ") {
|
|
||||||
String.format("%02X", it)
|
|
||||||
}
|
|
||||||
"Decoding (MAC=${scanResult.device.address}, nanos=${scanResult.timestampNanos}, rssi=${scanResult.rssi}): $dataHex"
|
|
||||||
}
|
|
||||||
|
|
||||||
return createSpecificDevice(scanResult, pm, identifier)
|
return createSpecificDevice(scanResult, pm, identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSpecificDevice(
|
private fun createSpecificDevice(
|
||||||
scanResult: ScanResult,
|
scanResult: BleScanResult,
|
||||||
pm: ProximityPairing.Message,
|
pm: ProximityPairing.Message,
|
||||||
identifier: PodDevice.Id
|
identifier: PodDevice.Id
|
||||||
): ApplePods {
|
): ApplePods {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ interface ApplePods : PodDevice {
|
|||||||
val proximityMessage: ProximityPairing.Message
|
val proximityMessage: ProximityPairing.Message
|
||||||
|
|
||||||
override val rawData: ByteArray
|
override val rawData: ByteArray
|
||||||
get() = scanResult.scanRecord!!.getManufacturerSpecificData(ContinuityProtocol.APPLE_COMPANY_IDENTIFIER)!!
|
get() = scanResult.getManufacturerSpecificData(ContinuityProtocol.APPLE_COMPANY_IDENTIFIER)!!
|
||||||
|
|
||||||
// We start counting at the airpods prefix byte
|
// We start counting at the airpods prefix byte
|
||||||
val rawPrefix: UByte
|
val rawPrefix: UByte
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package eu.darken.capod.pods.core.apple
|
package eu.darken.capod.pods.core.apple
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
data class UnknownAppleDevice constructor(
|
data class UnknownAppleDevice(
|
||||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||||
override val lastSeenAt: Instant = Instant.now(),
|
override val lastSeenAt: Instant = Instant.now(),
|
||||||
override val scanResult: ScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message
|
override val proximityMessage: ProximityPairing.Message
|
||||||
) : ApplePods {
|
) : ApplePods {
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package eu.darken.capod.pods.core.apple.airpods
|
package eu.darken.capod.pods.core.apple.airpods
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
@@ -11,7 +11,7 @@ import java.time.Instant
|
|||||||
data class AirPodsGen1 constructor(
|
data class AirPodsGen1 constructor(
|
||||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||||
override val lastSeenAt: Instant = Instant.now(),
|
override val lastSeenAt: Instant = Instant.now(),
|
||||||
override val scanResult: ScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
private val cachedBatteryPercentage: Float?
|
private val cachedBatteryPercentage: Float?
|
||||||
) : DualApplePods {
|
) : DualApplePods {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package eu.darken.capod.pods.core.apple.airpods
|
package eu.darken.capod.pods.core.apple.airpods
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
@@ -11,7 +11,7 @@ import java.time.Instant
|
|||||||
data class AirPodsGen2 constructor(
|
data class AirPodsGen2 constructor(
|
||||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||||
override val lastSeenAt: Instant = Instant.now(),
|
override val lastSeenAt: Instant = Instant.now(),
|
||||||
override val scanResult: ScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
private val cachedBatteryPercentage: Float?,
|
private val cachedBatteryPercentage: Float?,
|
||||||
) : DualApplePods {
|
) : DualApplePods {
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package eu.darken.capod.pods.core.apple.airpods
|
package eu.darken.capod.pods.core.apple.airpods
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
data class AirPodsMax constructor(
|
data class AirPodsMax(
|
||||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||||
override val lastSeenAt: Instant = Instant.now(),
|
override val lastSeenAt: Instant = Instant.now(),
|
||||||
override val scanResult: ScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
) : SingleApplePods {
|
) : SingleApplePods {
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package eu.darken.capod.pods.core.apple.airpods
|
package eu.darken.capod.pods.core.apple.airpods
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
data class AirPodsPro constructor(
|
data class AirPodsPro(
|
||||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||||
override val lastSeenAt: Instant = Instant.now(),
|
override val lastSeenAt: Instant = Instant.now(),
|
||||||
override val scanResult: ScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
private val cachedBatteryPercentage: Float?,
|
private val cachedBatteryPercentage: Float?,
|
||||||
) : DualApplePods {
|
) : DualApplePods {
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package eu.darken.capod.pods.core.apple.beats
|
package eu.darken.capod.pods.core.apple.beats
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
data class BeatsFlex constructor(
|
data class BeatsFlex(
|
||||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||||
override val lastSeenAt: Instant = Instant.now(),
|
override val lastSeenAt: Instant = Instant.now(),
|
||||||
override val scanResult: ScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message
|
override val proximityMessage: ProximityPairing.Message
|
||||||
) : BasicSingleApplePods {
|
) : BasicSingleApplePods {
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package eu.darken.capod.pods.core.apple.beats
|
package eu.darken.capod.pods.core.apple.beats
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
data class BeatsSolo3 constructor(
|
data class BeatsSolo3(
|
||||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||||
override val lastSeenAt: Instant = Instant.now(),
|
override val lastSeenAt: Instant = Instant.now(),
|
||||||
override val scanResult: ScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message
|
override val proximityMessage: ProximityPairing.Message
|
||||||
) : BasicSingleApplePods {
|
) : BasicSingleApplePods {
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package eu.darken.capod.pods.core.apple.beats
|
package eu.darken.capod.pods.core.apple.beats
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
data class BeatsStudio3 constructor(
|
data class BeatsStudio3(
|
||||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||||
override val lastSeenAt: Instant = Instant.now(),
|
override val lastSeenAt: Instant = Instant.now(),
|
||||||
override val scanResult: ScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message
|
override val proximityMessage: ProximityPairing.Message
|
||||||
) : BasicSingleApplePods {
|
) : BasicSingleApplePods {
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package eu.darken.capod.pods.core.apple.beats
|
package eu.darken.capod.pods.core.apple.beats
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
data class BeatsX constructor(
|
data class BeatsX(
|
||||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||||
override val lastSeenAt: Instant = Instant.now(),
|
override val lastSeenAt: Instant = Instant.now(),
|
||||||
override val scanResult: ScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message
|
override val proximityMessage: ProximityPairing.Message
|
||||||
) : BasicSingleApplePods {
|
) : BasicSingleApplePods {
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package eu.darken.capod.pods.core.apple.beats
|
package eu.darken.capod.pods.core.apple.beats
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
data class PowerBeats3 constructor(
|
data class PowerBeats3(
|
||||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||||
override val lastSeenAt: Instant = Instant.now(),
|
override val lastSeenAt: Instant = Instant.now(),
|
||||||
override val scanResult: ScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message
|
override val proximityMessage: ProximityPairing.Message
|
||||||
) : BasicSingleApplePods {
|
) : BasicSingleApplePods {
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package eu.darken.capod.pods.core.apple.beats
|
package eu.darken.capod.pods.core.apple.beats
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
data class PowerBeatsPro constructor(
|
data class PowerBeatsPro(
|
||||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||||
override val lastSeenAt: Instant = Instant.now(),
|
override val lastSeenAt: Instant = Instant.now(),
|
||||||
override val scanResult: ScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
private val cachedBatteryPercentage: Float?,
|
private val cachedBatteryPercentage: Float?,
|
||||||
) : DualApplePods {
|
) : DualApplePods {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package eu.darken.capod.pods.core.apple.protocol
|
package eu.darken.capod.pods.core.apple.protocol
|
||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import android.os.ParcelUuid
|
import android.os.ParcelUuid
|
||||||
import dagger.Reusable
|
import dagger.Reusable
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
@@ -29,8 +29,8 @@ object ContinuityProtocol {
|
|||||||
|
|
||||||
@Reusable
|
@Reusable
|
||||||
class Decoder @Inject constructor() {
|
class Decoder @Inject constructor() {
|
||||||
fun decode(scanResult: ScanResult): List<Message> = scanResult.scanRecord
|
fun decode(scanResult: BleScanResult): List<Message> = scanResult
|
||||||
?.getManufacturerSpecificData(APPLE_COMPANY_IDENTIFIER)
|
.getManufacturerSpecificData(APPLE_COMPANY_IDENTIFIER)
|
||||||
?.let { data ->
|
?.let { data ->
|
||||||
val messages = mutableListOf<Message>()
|
val messages = mutableListOf<Message>()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M15,4l0,2l3,0l0,12l-3,0l0,2l5,0l0,-16z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M4,20l5,0l0,-2l-3,0l0,-12l3,0l0,-2l-5,0z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:autoMirrored="true">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M20.41,8.41l-4.83,-4.83C15.21,3.21 14.7,3 14.17,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V9.83C21,9.3 20.79,8.79 20.41,8.41zM7,7h7v2H7V7zM17,17H7v-2h10V17zM17,13H7v-2h10V13z" />
|
||||||
|
</vector>
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_item_settings"
|
android:id="@+id/menu_item_settings"
|
||||||
android:title="@string/settings_general_label" />
|
android:icon="@drawable/ic_baseline_settings_24"
|
||||||
<item
|
android:title="@string/settings_general_label"
|
||||||
android:id="@+id/menu_item_debuglog"
|
app:showAsAction="always" />
|
||||||
android:title="@string/debug_debuglog_record_action" />
|
|
||||||
</menu>
|
</menu>
|
||||||
@@ -103,4 +103,8 @@
|
|||||||
<string name="pods_single_basic_status_short">Headphones: %1$s</string>
|
<string name="pods_single_basic_status_short">Headphones: %1$s</string>
|
||||||
<string name="permission_background_location_label">ACCESS_BACKGROUND_LOCATION</string>
|
<string name="permission_background_location_label">ACCESS_BACKGROUND_LOCATION</string>
|
||||||
<string name="permission_background_location_description">Allows an app to access location in the background.</string>
|
<string name="permission_background_location_description">Allows an app to access location in the background.</string>
|
||||||
|
<string name="settings_fake_data_label">Fake data</string>
|
||||||
|
<string name="settings_fake_data_description">Show fake data, i.e. simulate device that don\'t exist.</string>
|
||||||
|
<string name="settings_debug_label">Debug settings</string>
|
||||||
|
<string name="settings_debug_description">Additional settings to help troubleshoot issues with the app.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:icon="@drawable/ic_baseline_text_snippet_24"
|
||||||
|
android:key="debug.log.record"
|
||||||
|
android:title="@string/debug_debuglog_record_action" />
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:icon="@drawable/ic_baseline_bug_report_24"
|
||||||
|
android:key="debug.mode.enabled"
|
||||||
|
android:summary="@string/settings_debug_mode_description"
|
||||||
|
android:title="@string/settings_debug_mode_label" />
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:icon="@drawable/ic_baseline_data_array_24"
|
||||||
|
android:key="debug.fakedata.enabled"
|
||||||
|
android:summary="@string/settings_fake_data_description"
|
||||||
|
android:title="@string/settings_fake_data_label" />
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
||||||
@@ -39,11 +39,12 @@
|
|||||||
android:summary="@string/settings_debug_autoreports_description"
|
android:summary="@string/settings_debug_autoreports_description"
|
||||||
android:title="@string/settings_debug_autoreports_label" />
|
android:title="@string/settings_debug_autoreports_label" />
|
||||||
|
|
||||||
<CheckBoxPreference
|
<Preference
|
||||||
android:icon="@drawable/ic_baseline_bug_report_24"
|
android:icon="@drawable/ic_baseline_bug_report_24"
|
||||||
android:key="debug.mode.enabled"
|
android:key="debug.settings"
|
||||||
android:summary="@string/settings_debug_mode_description"
|
android:fragment="eu.darken.capod.main.ui.settings.general.debug.DebugSettingsFragment"
|
||||||
android:title="@string/settings_debug_mode_label" />
|
android:summary="@string/settings_debug_description"
|
||||||
|
android:title="@string/settings_debug_label" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
package eu.darken.capod.pods.core.apple
|
package eu.darken.capod.pods.core.apple
|
||||||
|
|
||||||
import android.bluetooth.BluetoothDevice
|
|
||||||
import android.bluetooth.le.ScanRecord
|
|
||||||
import android.bluetooth.le.ScanResult
|
|
||||||
import eu.darken.capod.common.SystemClockWrap
|
import eu.darken.capod.common.SystemClockWrap
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
|
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.impl.annotations.MockK
|
|
||||||
import io.mockk.mockkObject
|
import io.mockk.mockkObject
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import testhelper.BaseTest
|
import testhelper.BaseTest
|
||||||
|
|
||||||
abstract class BaseAirPodsTest : BaseTest() {
|
abstract class BaseAirPodsTest : BaseTest() {
|
||||||
|
|
||||||
@MockK lateinit var scanResult: ScanResult
|
private val baseBleScanResult = BleScanResult(
|
||||||
@MockK lateinit var scanRecord: ScanRecord
|
address = "77:49:4C:D8:25:0C",
|
||||||
@MockK lateinit var device: BluetoothDevice
|
rssi = -66,
|
||||||
|
generatedAtNanos = 136136027721826,
|
||||||
|
manufacturerSpecificData = emptyMap()
|
||||||
|
)
|
||||||
|
|
||||||
val factory = AppleFactory(
|
val factory = AppleFactory(
|
||||||
proximityPairingDecoder = ProximityPairing.Decoder(),
|
proximityPairingDecoder = ProximityPairing.Decoder(),
|
||||||
@@ -28,11 +28,6 @@ abstract class BaseAirPodsTest : BaseTest() {
|
|||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
MockKAnnotations.init(this)
|
MockKAnnotations.init(this)
|
||||||
every { scanResult.scanRecord } returns scanRecord
|
|
||||||
every { scanResult.rssi } returns -66
|
|
||||||
every { scanResult.timestampNanos } returns 136136027721826
|
|
||||||
every { scanResult.device } returns device
|
|
||||||
every { device.address } returns "77:49:4C:D8:25:0C"
|
|
||||||
|
|
||||||
mockkObject(SystemClockWrap)
|
mockkObject(SystemClockWrap)
|
||||||
every { SystemClockWrap.elapsedRealtimeNanos } returns 1000L
|
every { SystemClockWrap.elapsedRealtimeNanos } returns 1000L
|
||||||
@@ -45,11 +40,11 @@ abstract class BaseAirPodsTest : BaseTest() {
|
|||||||
.replace("<", "")
|
.replace("<", "")
|
||||||
require(trimmed.length % 2 == 0) { "Not a HEX string" }
|
require(trimmed.length % 2 == 0) { "Not a HEX string" }
|
||||||
val bytes = trimmed.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
val bytes = trimmed.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||||
mockData(bytes)
|
val result = mockData(bytes)
|
||||||
block.invoke(factory.create(scanResult) as T)
|
block.invoke(factory.create(result) as T)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mockData(hex: String) {
|
fun mockData(hex: String): BleScanResult {
|
||||||
val trimmed = hex
|
val trimmed = hex
|
||||||
.replace(" ", "")
|
.replace(" ", "")
|
||||||
.replace(">", "")
|
.replace(">", "")
|
||||||
@@ -59,7 +54,9 @@ abstract class BaseAirPodsTest : BaseTest() {
|
|||||||
return mockData(bytes)
|
return mockData(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mockData(data: ByteArray) {
|
fun mockData(data: ByteArray): BleScanResult = baseBleScanResult.copy(
|
||||||
every { scanRecord.getManufacturerSpecificData(ContinuityProtocol.APPLE_COMPANY_IDENTIFIER) } returns data
|
manufacturerSpecificData = mutableMapOf<Int, ByteArray>().apply {
|
||||||
}
|
this[ContinuityProtocol.APPLE_COMPANY_IDENTIFIER] = data
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -26,11 +26,9 @@ class SingleApplePodsTest : BaseAirPodsTest() {
|
|||||||
create<AirPodsMax>("07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E") {
|
create<AirPodsMax>("07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E") {
|
||||||
batteryHeadsetPercent shouldBe 0.5f
|
batteryHeadsetPercent shouldBe 0.5f
|
||||||
|
|
||||||
isCaseCharging shouldBe false
|
|
||||||
isHeadsetBeingCharged shouldBe false
|
isHeadsetBeingCharged shouldBe false
|
||||||
|
|
||||||
isHeadphonesBeingWorn shouldBe true
|
isHeadphonesBeingWorn shouldBe true
|
||||||
batteryCasePercent shouldBe 0.0f
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,11 +22,9 @@ class AirPodsMaxTest : BaseAirPodsTest() {
|
|||||||
|
|
||||||
batteryHeadsetPercent shouldBe 0.4f
|
batteryHeadsetPercent shouldBe 0.4f
|
||||||
|
|
||||||
isCaseCharging shouldBe false
|
|
||||||
isHeadsetBeingCharged shouldBe false
|
isHeadsetBeingCharged shouldBe false
|
||||||
|
|
||||||
isHeadphonesBeingWorn shouldBe true
|
isHeadphonesBeingWorn shouldBe true
|
||||||
batteryCasePercent shouldBe 0.0f
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,11 +43,9 @@ class AirPodsMaxTest : BaseAirPodsTest() {
|
|||||||
|
|
||||||
batteryHeadsetPercent shouldBe 0.5f
|
batteryHeadsetPercent shouldBe 0.5f
|
||||||
|
|
||||||
isCaseCharging shouldBe false
|
|
||||||
isHeadsetBeingCharged shouldBe false
|
isHeadsetBeingCharged shouldBe false
|
||||||
|
|
||||||
isHeadphonesBeingWorn shouldBe true
|
isHeadphonesBeingWorn shouldBe true
|
||||||
batteryCasePercent shouldBe 0.0f
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isCaseCharging shouldBe false
|
isCaseCharging shouldBe false
|
||||||
isRightPodCharging shouldBe false
|
isRightPodCharging shouldBe false
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
batteryCasePercent shouldBe null
|
batteryCasePercent shouldBe 0.6f
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user