mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Refactoring of device creation, more autonomy.
This commit is contained in:
@@ -191,6 +191,8 @@ dependencies {
|
||||
|
||||
kapt "androidx.hilt:hilt-compiler:1.0.0"
|
||||
implementation 'androidx.hilt:hilt-common:1.0.0'
|
||||
kaptTest "androidx.hilt:hilt-compiler:1.0.0"
|
||||
testImplementation 'androidx.hilt:hilt-common:1.0.0'
|
||||
|
||||
// Support libs
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
|
||||
@@ -2,6 +2,8 @@ package eu.darken.capod.pods.core
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import java.time.Instant
|
||||
@@ -12,6 +14,8 @@ interface PodDevice {
|
||||
|
||||
val identifier: Id
|
||||
|
||||
val model: Model
|
||||
|
||||
val lastSeenAt: Instant
|
||||
|
||||
val scanResult: BleScanResult
|
||||
@@ -31,12 +35,60 @@ interface PodDevice {
|
||||
val rawDataHex: String
|
||||
get() = rawData.joinToString(separator = " ") { String.format("%02X", it) }
|
||||
|
||||
fun getLabel(context: Context): String
|
||||
fun getLabel(context: Context): String = model.label
|
||||
|
||||
@get:DrawableRes
|
||||
val iconRes: Int
|
||||
get() = R.drawable.ic_device_generic_earbuds
|
||||
get() = model.iconRes
|
||||
|
||||
@JvmInline
|
||||
value class Id(private val id: UUID = UUID.randomUUID())
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class Model(
|
||||
val label: String,
|
||||
@DrawableRes val iconRes: Int = R.drawable.ic_device_generic_earbuds,
|
||||
) {
|
||||
@Json(name = "airpods.gen1") AIRPODS_GEN1(
|
||||
label = "AirPods (Gen 1)",
|
||||
iconRes = R.drawable.ic_device_airpods_gen1,
|
||||
),
|
||||
@Json(name = "airpods.gen2") AIRPODS_GEN2(
|
||||
"AirPods (Gen 2)",
|
||||
R.drawable.ic_device_airpods_gen2,
|
||||
),
|
||||
@Json(name = "airpods.gen3") AIRPODS_GEN3(
|
||||
"AirPods (Gen 3)",
|
||||
R.drawable.ic_device_airpods_gen2,
|
||||
),
|
||||
@Json(name = "airpods.pro") AIRPODS_PRO(
|
||||
"AirPods Pro",
|
||||
R.drawable.ic_device_airpods_gen2
|
||||
),
|
||||
@Json(name = "airpods.max") AIRPODS_MAX(
|
||||
"AirPods Max",
|
||||
R.drawable.ic_device_generic_headphones
|
||||
),
|
||||
@Json(name = "beats.flex") BEATS_FLEX(
|
||||
"Beats Flex"
|
||||
),
|
||||
@Json(name = "beats.solo.3") BEATS_SOLO_3(
|
||||
"Beats Solo 3"
|
||||
),
|
||||
@Json(name = "beats.studio.3") BEATS_STUDIO_3(
|
||||
"Beats Studio 3"
|
||||
),
|
||||
@Json(name = "beats.x") BEATS_X(
|
||||
"Beats X"
|
||||
),
|
||||
@Json(name = "beats.powerbeats.3") POWERBEATS_3(
|
||||
"Power Beats 3"
|
||||
),
|
||||
@Json(name = "beats.powerbeats.pro") POWERBEATS_PRO(
|
||||
"Power Beats Pro"
|
||||
),
|
||||
@Json(name = "unknown") UNKNOWN(
|
||||
"Unknown"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
package eu.darken.capod.pods.core.apple
|
||||
|
||||
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.logging.Logging.Priority.*
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.HasCase
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.airpods.*
|
||||
import eu.darken.capod.pods.core.apple.beats.*
|
||||
@@ -15,7 +13,6 @@ import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -24,38 +21,13 @@ import javax.inject.Singleton
|
||||
class AppleFactory @Inject constructor(
|
||||
private val continuityProtocolDecoder: ContinuityProtocol.Decoder,
|
||||
private val proximityPairingDecoder: ProximityPairing.Decoder,
|
||||
private val podFactories: @JvmSuppressWildcards Set<ApplePods.Factory>,
|
||||
private val unknownAppleFactory: UnknownAppleDevice.Factory,
|
||||
) {
|
||||
|
||||
data class KnownDevice(
|
||||
val identifier: PodDevice.Id,
|
||||
val scanResult: BleScanResult,
|
||||
val message: ProximityPairing.Message,
|
||||
) {
|
||||
val address: String
|
||||
get() = scanResult.address
|
||||
|
||||
val rssi: Int
|
||||
get() = scanResult.rssi
|
||||
|
||||
val timestampNanos: Duration
|
||||
get() = Duration.ofNanos(scanResult.generatedAtNanos)
|
||||
|
||||
fun isOlderThan(age: Duration): Boolean {
|
||||
val now = Duration.ofNanos(SystemClockWrap.elapsedRealtimeNanos)
|
||||
return now - timestampNanos > age
|
||||
}
|
||||
}
|
||||
|
||||
private val lock = Mutex()
|
||||
private val knownDevs = mutableMapOf<PodDevice.Id, KnownDevice>()
|
||||
|
||||
data class ValueCache(
|
||||
val caseBatteryPercentage: Float?
|
||||
)
|
||||
|
||||
private val cachedValues = mutableMapOf<PodDevice.Id, ValueCache>()
|
||||
|
||||
private suspend fun getMessage(scanResult: BleScanResult): ProximityPairing.Message? {
|
||||
private fun getMessage(scanResult: BleScanResult): ProximityPairing.Message? {
|
||||
val messages = try {
|
||||
continuityProtocolDecoder.decode(scanResult)
|
||||
} catch (e: Exception) {
|
||||
@@ -80,162 +52,29 @@ class AppleFactory @Inject constructor(
|
||||
return proximityMessage
|
||||
}
|
||||
|
||||
private suspend fun recognizeDevice(scanResult: BleScanResult, message: ProximityPairing.Message): PodDevice.Id {
|
||||
val address = scanResult.address
|
||||
|
||||
var identifier: PodDevice.Id? = null
|
||||
|
||||
knownDevs.values.firstOrNull { it.address == address }?.let {
|
||||
log(TAG, VERBOSE) { "recognizeDevice: Recovered previous ID via address: $it" }
|
||||
knownDevs[it.identifier] = it.copy(
|
||||
scanResult = scanResult,
|
||||
message = message,
|
||||
)
|
||||
identifier = it.identifier
|
||||
}
|
||||
|
||||
if (identifier == null) {
|
||||
val currentMarkers = message.getRecogMarkers()
|
||||
knownDevs.values
|
||||
.firstOrNull { it.message.getRecogMarkers() == currentMarkers }
|
||||
?.let {
|
||||
log(TAG, DEBUG) { "recognizeDevice: Close match based similarity markers." }
|
||||
log(TAG, DEBUG) { "recognizeDevice: Old marker: ${it.message.getRecogMarkers()}" }
|
||||
log(TAG, DEBUG) { "recognizeDevice: New marker: $currentMarkers" }
|
||||
knownDevs[it.identifier] = it.copy(
|
||||
scanResult = scanResult,
|
||||
message = message,
|
||||
)
|
||||
identifier = it.identifier
|
||||
}
|
||||
}
|
||||
|
||||
if (identifier == null) {
|
||||
log(TAG, VERBOSE) { "recognizeDevice: Mapping as new device" }
|
||||
identifier = PodDevice.Id()
|
||||
}
|
||||
|
||||
knownDevs[identifier!!] = KnownDevice(
|
||||
identifier = identifier!!,
|
||||
scanResult = scanResult,
|
||||
message = message
|
||||
)
|
||||
|
||||
knownDevs.values.toList().forEach { knownDevice ->
|
||||
if (knownDevice.isOlderThan(Duration.ofSeconds(20))) {
|
||||
log(TAG, VERBOSE) { "recognizeDevice: Removing stale known device: $knownDevice" }
|
||||
knownDevs.remove(knownDevice.identifier)
|
||||
}
|
||||
}
|
||||
|
||||
return identifier!!
|
||||
}
|
||||
|
||||
suspend fun create(scanResult: BleScanResult): PodDevice? = lock.withLock {
|
||||
val pm = getMessage(scanResult) ?: return@withLock null
|
||||
|
||||
val identifier = recognizeDevice(scanResult, pm)
|
||||
|
||||
log(TAG, INFO) { "Decoding $scanResult" }
|
||||
|
||||
return createSpecificDevice(scanResult, pm, identifier)
|
||||
}
|
||||
var factory = podFactories.firstOrNull { it.isResponsible(pm) }
|
||||
|
||||
private fun createSpecificDevice(
|
||||
scanResult: BleScanResult,
|
||||
pm: ProximityPairing.Message,
|
||||
identifier: PodDevice.Id
|
||||
): ApplePods {
|
||||
|
||||
val cachedCaseBattery = cachedValues[identifier]?.caseBatteryPercentage
|
||||
|
||||
val dm = (((pm.data[1].toInt() and 255) shl 8) or (pm.data[2].toInt() and 255)).toUShort()
|
||||
val dmDirty = pm.data[1]
|
||||
|
||||
val specificDevice = when {
|
||||
dm == AirPodsGen1.DEVICE_CODE -> AirPodsGen1(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm,
|
||||
cachedBatteryPercentage = cachedCaseBattery,
|
||||
if (factory == null) {
|
||||
Bugs.report(
|
||||
tag = TAG,
|
||||
message = "Unknown proximity message type",
|
||||
exception = IllegalArgumentException("Unknown ProximityMessage: $pm")
|
||||
)
|
||||
dm == AirPodsGen2.DEVICE_CODE -> AirPodsGen2(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm,
|
||||
cachedBatteryPercentage = cachedCaseBattery,
|
||||
)
|
||||
dm == AirPodsGen3.DEVICE_CODE -> AirPodsGen3(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm,
|
||||
cachedBatteryPercentage = cachedCaseBattery,
|
||||
)
|
||||
dm == AirPodsPro.DEVICE_CODE -> AirPodsPro(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm,
|
||||
cachedBatteryPercentage = cachedCaseBattery,
|
||||
)
|
||||
dmDirty == PowerBeatsPro.DEVICE_CODE_DIRTY -> PowerBeatsPro(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm,
|
||||
cachedBatteryPercentage = cachedCaseBattery,
|
||||
)
|
||||
dmDirty == AirPodsMax.DEVICE_CODE_DIRTY -> AirPodsMax(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm,
|
||||
)
|
||||
dm == PowerBeats3.DEVICE_CODE -> PowerBeats3(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm
|
||||
)
|
||||
dm == BeatsSolo3.DEVICE_CODE -> BeatsSolo3(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm
|
||||
)
|
||||
dmDirty == BeatsStudio3.DEVICE_CODE_DIRTY -> BeatsStudio3(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm
|
||||
)
|
||||
dm == BeatsX.DEVICE_CODE -> BeatsX(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm
|
||||
)
|
||||
dm == BeatsFlex.DEVICE_CODE -> BeatsFlex(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm
|
||||
)
|
||||
|
||||
else -> {
|
||||
Bugs.report(
|
||||
tag = TAG,
|
||||
message = "Unknown proximity message type",
|
||||
exception = IllegalArgumentException("Unknown ProximityMessage: $pm")
|
||||
)
|
||||
UnknownAppleDevice(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm
|
||||
)
|
||||
}
|
||||
factory = unknownAppleFactory
|
||||
}
|
||||
|
||||
cachedValues[identifier] = ValueCache(
|
||||
caseBatteryPercentage = (specificDevice as? HasCase)?.batteryCasePercent
|
||||
return factory.create(
|
||||
scanResult = scanResult,
|
||||
proximityMessage = pm,
|
||||
)
|
||||
|
||||
return specificDevice
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("Pod", "AirPods", "Factory")
|
||||
private val TAG = logTag("Pod", "Apple", "Factory")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package eu.darken.capod.pods.core.apple
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import eu.darken.capod.pods.core.apple.airpods.*
|
||||
import eu.darken.capod.pods.core.apple.beats.*
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
abstract class AppleFactoryModule {
|
||||
|
||||
@Binds @IntoSet abstract fun airPodsGen1(factory: AirPodsGen1.Factory): ApplePods.Factory
|
||||
@Binds @IntoSet abstract fun airPodsGen2(factory: AirPodsGen2.Factory): ApplePods.Factory
|
||||
@Binds @IntoSet abstract fun airPodsGen3(factory: AirPodsGen3.Factory): ApplePods.Factory
|
||||
|
||||
@Binds @IntoSet abstract fun airPodsPro(factory: AirPodsPro.Factory): ApplePods.Factory
|
||||
@Binds @IntoSet abstract fun airPodsMax(factory: AirPodsMax.Factory): ApplePods.Factory
|
||||
|
||||
@Binds @IntoSet abstract fun beatsFlex(factory: BeatsFlex.Factory): ApplePods.Factory
|
||||
@Binds @IntoSet abstract fun beatsSolo3(factory: BeatsSolo3.Factory): ApplePods.Factory
|
||||
@Binds @IntoSet abstract fun beatsStudio3(factory: BeatsStudio3.Factory): ApplePods.Factory
|
||||
@Binds @IntoSet abstract fun beatsX(factory: BeatsX.Factory): ApplePods.Factory
|
||||
@Binds @IntoSet abstract fun powerBeats3(factory: PowerBeats3.Factory): ApplePods.Factory
|
||||
@Binds @IntoSet abstract fun powerBeatsPro(factory: PowerBeatsPro.Factory): ApplePods.Factory
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
package eu.darken.capod.pods.core.apple
|
||||
|
||||
import eu.darken.capod.common.SystemClockWrap
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.DEBUG
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Duration
|
||||
|
||||
interface ApplePods : PodDevice {
|
||||
|
||||
@@ -35,4 +41,104 @@ interface ApplePods : PodDevice {
|
||||
|
||||
val rawSuffix: UByte
|
||||
get() = proximityMessage.data[8]
|
||||
|
||||
|
||||
abstract class Factory(private val tag: String) {
|
||||
|
||||
internal data class ValueCache(
|
||||
val caseBatteryPercentage: Float?
|
||||
)
|
||||
|
||||
internal val cachedValues = mutableMapOf<PodDevice.Id, ValueCache>()
|
||||
|
||||
internal data class KnownDevice(
|
||||
val identifier: PodDevice.Id,
|
||||
val scanResult: BleScanResult,
|
||||
val message: ProximityPairing.Message,
|
||||
) {
|
||||
val address: String
|
||||
get() = scanResult.address
|
||||
|
||||
val rssi: Int
|
||||
get() = scanResult.rssi
|
||||
|
||||
val timestampNanos: Duration
|
||||
get() = Duration.ofNanos(scanResult.generatedAtNanos)
|
||||
|
||||
fun isOlderThan(age: Duration): Boolean {
|
||||
val now = Duration.ofNanos(SystemClockWrap.elapsedRealtimeNanos)
|
||||
return now - timestampNanos > age
|
||||
}
|
||||
}
|
||||
|
||||
private val knownDevs = mutableMapOf<PodDevice.Id, KnownDevice>()
|
||||
|
||||
internal fun recognizeDevice(scanResult: BleScanResult, message: ProximityPairing.Message): PodDevice.Id {
|
||||
val address = scanResult.address
|
||||
|
||||
var identifier: PodDevice.Id? = null
|
||||
|
||||
knownDevs.values.firstOrNull { it.address == address }?.let {
|
||||
log(tag, VERBOSE) { "recognizeDevice: Recovered previous ID via address: $it" }
|
||||
knownDevs[it.identifier] = it.copy(
|
||||
scanResult = scanResult,
|
||||
message = message,
|
||||
)
|
||||
identifier = it.identifier
|
||||
}
|
||||
|
||||
if (identifier == null) {
|
||||
val currentMarkers = message.getRecogMarkers()
|
||||
knownDevs.values
|
||||
.firstOrNull { it.message.getRecogMarkers() == currentMarkers }
|
||||
?.let {
|
||||
log(tag, DEBUG) { "recognizeDevice: Close match based similarity markers." }
|
||||
log(tag, DEBUG) { "recognizeDevice: Old marker: ${it.message.getRecogMarkers()}" }
|
||||
log(tag, DEBUG) { "recognizeDevice: New marker: $currentMarkers" }
|
||||
knownDevs[it.identifier] = it.copy(
|
||||
scanResult = scanResult,
|
||||
message = message,
|
||||
)
|
||||
identifier = it.identifier
|
||||
}
|
||||
}
|
||||
|
||||
if (identifier == null) {
|
||||
log(tag, VERBOSE) { "recognizeDevice: Mapping as new device" }
|
||||
identifier = PodDevice.Id()
|
||||
}
|
||||
|
||||
knownDevs[identifier!!] = KnownDevice(
|
||||
identifier = identifier!!,
|
||||
scanResult = scanResult,
|
||||
message = message
|
||||
)
|
||||
|
||||
knownDevs.values.toList().forEach { knownDevice ->
|
||||
if (knownDevice.isOlderThan(Duration.ofSeconds(20))) {
|
||||
log(tag, VERBOSE) { "recognizeDevice: Removing stale known device: $knownDevice" }
|
||||
knownDevs.remove(knownDevice.identifier)
|
||||
}
|
||||
}
|
||||
|
||||
return identifier!!
|
||||
}
|
||||
|
||||
data class ModelInfo(
|
||||
val full: UShort,
|
||||
val dirty: UByte,
|
||||
)
|
||||
|
||||
fun ProximityPairing.Message.getModelInfo(): ModelInfo = ModelInfo(
|
||||
full = (((data[1].toInt() and 255) shl 8) or (data[2].toInt() and 255)).toUShort(),
|
||||
dirty = data[1]
|
||||
)
|
||||
|
||||
abstract fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean
|
||||
|
||||
abstract fun create(
|
||||
scanResult: BleScanResult,
|
||||
proximityMessage: ProximityPairing.Message,
|
||||
): ApplePods
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,11 @@ package eu.darken.capod.pods.core.apple
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class UnknownAppleDevice(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
@@ -14,8 +16,28 @@ data class UnknownAppleDevice(
|
||||
override val proximityMessage: ProximityPairing.Message
|
||||
) : ApplePods {
|
||||
|
||||
override fun getLabel(context: Context): String {
|
||||
return context.getString(R.string.pods_unknown_label)
|
||||
override val model: PodDevice.Model = PodDevice.Model.UNKNOWN
|
||||
|
||||
override fun getLabel(context: Context): String = context.getString(R.string.pods_unknown_label)
|
||||
|
||||
class Factory @Inject constructor() : ApplePods.Factory(TAG) {
|
||||
override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean = true
|
||||
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
proximityMessage: ProximityPairing.Message,
|
||||
): ApplePods {
|
||||
val identifier = recognizeDevice(scanResult, proximityMessage)
|
||||
|
||||
return UnknownAppleDevice(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = proximityMessage,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("PodDevice", "Apple", "Unknown")
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package eu.darken.capod.pods.core.apple.airpods
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class AirPodsGen1 constructor(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
@@ -16,15 +17,36 @@ data class AirPodsGen1 constructor(
|
||||
private val cachedBatteryPercentage: Float?
|
||||
) : DualApplePods {
|
||||
|
||||
override fun getLabel(context: Context): String = "AirPods (Gen 1)"
|
||||
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.ic_device_airpods_gen1
|
||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN1
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
class Factory @Inject constructor() : ApplePods.Factory(TAG) {
|
||||
|
||||
override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean =
|
||||
proximityMessage.getModelInfo().full == DEVICE_CODE
|
||||
|
||||
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
|
||||
val identifier = recognizeDevice(scanResult, proximityMessage)
|
||||
|
||||
val device = AirPodsGen1(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = proximityMessage,
|
||||
cachedBatteryPercentage = cachedValues[identifier]?.caseBatteryPercentage
|
||||
)
|
||||
|
||||
cachedValues[identifier] = ValueCache(
|
||||
caseBatteryPercentage = device.batteryCasePercent
|
||||
)
|
||||
|
||||
return device
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEVICE_CODE = 0x0220.toUShort()
|
||||
private val DEVICE_CODE = 0x0220.toUShort()
|
||||
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Gen1")
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package eu.darken.capod.pods.core.apple.airpods
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class AirPodsGen2 constructor(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
@@ -16,15 +17,36 @@ data class AirPodsGen2 constructor(
|
||||
private val cachedBatteryPercentage: Float?,
|
||||
) : DualApplePods {
|
||||
|
||||
override fun getLabel(context: Context): String = "AirPods (Gen 2)"
|
||||
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.ic_device_airpods_gen2
|
||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN2
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
class Factory @Inject constructor() : ApplePods.Factory(TAG) {
|
||||
|
||||
override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean =
|
||||
proximityMessage.getModelInfo().full == DEVICE_CODE
|
||||
|
||||
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
|
||||
val identifier = recognizeDevice(scanResult, proximityMessage)
|
||||
|
||||
val device = AirPodsGen2(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = proximityMessage,
|
||||
cachedBatteryPercentage = cachedValues[identifier]?.caseBatteryPercentage
|
||||
)
|
||||
|
||||
cachedValues[identifier] = ValueCache(
|
||||
caseBatteryPercentage = device.batteryCasePercent
|
||||
)
|
||||
|
||||
return device
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEVICE_CODE = 0x0F20.toUShort()
|
||||
private val DEVICE_CODE = 0x0F20.toUShort()
|
||||
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Gen2")
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package eu.darken.capod.pods.core.apple.airpods
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class AirPodsGen3 constructor(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
@@ -15,16 +16,37 @@ data class AirPodsGen3 constructor(
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
private val cachedBatteryPercentage: Float?,
|
||||
) : DualApplePods {
|
||||
|
||||
override fun getLabel(context: Context): String = "AirPods (Gen 3)"
|
||||
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.ic_device_airpods_gen2
|
||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN3
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
class Factory @Inject constructor() : ApplePods.Factory(TAG) {
|
||||
|
||||
override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean =
|
||||
proximityMessage.getModelInfo().full == DEVICE_CODE
|
||||
|
||||
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
|
||||
val identifier = recognizeDevice(scanResult, proximityMessage)
|
||||
|
||||
val device = AirPodsGen3(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = proximityMessage,
|
||||
cachedBatteryPercentage = cachedValues[identifier]?.caseBatteryPercentage
|
||||
)
|
||||
|
||||
cachedValues[identifier] = ValueCache(
|
||||
caseBatteryPercentage = device.batteryCasePercent
|
||||
)
|
||||
|
||||
return device
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEVICE_CODE = 0x1320.toUShort()
|
||||
private val DEVICE_CODE = 0x1320.toUShort()
|
||||
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Gen3")
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package eu.darken.capod.pods.core.apple.airpods
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class AirPodsMax(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
@@ -15,12 +16,27 @@ data class AirPodsMax(
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
) : SingleApplePods {
|
||||
|
||||
override fun getLabel(context: Context): String = "AirPods Max"
|
||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_MAX
|
||||
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.ic_device_generic_headphones
|
||||
class Factory @Inject constructor() : ApplePods.Factory(TAG) {
|
||||
|
||||
override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean =
|
||||
proximityMessage.getModelInfo().dirty == DEVICE_CODE_DIRTY
|
||||
|
||||
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
|
||||
val identifier = recognizeDevice(scanResult, proximityMessage)
|
||||
|
||||
return AirPodsMax(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = proximityMessage,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEVICE_CODE_DIRTY = 10.toUByte()
|
||||
private val DEVICE_CODE_DIRTY = 10.toUByte()
|
||||
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Max")
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package eu.darken.capod.pods.core.apple.airpods
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class AirPodsPro(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
@@ -16,17 +17,37 @@ data class AirPodsPro(
|
||||
private val cachedBatteryPercentage: Float?,
|
||||
) : DualApplePods {
|
||||
|
||||
override fun getLabel(context: Context): String {
|
||||
return "AirPods Pro"
|
||||
}
|
||||
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.ic_device_airpods_gen2
|
||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_PRO
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
class Factory @Inject constructor() : ApplePods.Factory(TAG) {
|
||||
|
||||
override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean =
|
||||
proximityMessage.getModelInfo().full == DEVICE_CODE
|
||||
|
||||
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
|
||||
val identifier = recognizeDevice(scanResult, proximityMessage)
|
||||
|
||||
val device = AirPodsPro(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = proximityMessage,
|
||||
cachedBatteryPercentage = cachedValues[identifier]?.caseBatteryPercentage
|
||||
)
|
||||
|
||||
cachedValues[identifier] = ValueCache(
|
||||
caseBatteryPercentage = device.batteryCasePercent
|
||||
)
|
||||
|
||||
return device
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEVICE_CODE = 0x0e20.toUShort()
|
||||
private val DEVICE_CODE = 0x0e20.toUShort()
|
||||
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Pro")
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package eu.darken.capod.pods.core.apple.beats
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class BeatsFlex(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
@@ -15,12 +16,27 @@ data class BeatsFlex(
|
||||
override val proximityMessage: ProximityPairing.Message
|
||||
) : BasicSingleApplePods {
|
||||
|
||||
override fun getLabel(context: Context): String = "Beats Flex"
|
||||
override val model: PodDevice.Model = PodDevice.Model.BEATS_FLEX
|
||||
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.ic_device_generic_earbuds
|
||||
class Factory @Inject constructor() : ApplePods.Factory(TAG) {
|
||||
|
||||
override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean =
|
||||
proximityMessage.getModelInfo().full == DEVICE_CODE
|
||||
|
||||
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
|
||||
val identifier = recognizeDevice(scanResult, proximityMessage)
|
||||
|
||||
return BeatsFlex(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = proximityMessage,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEVICE_CODE = 0x1020.toUShort()
|
||||
private val DEVICE_CODE = 0x1020.toUShort()
|
||||
private val TAG = logTag("PodDevice", "Beats", "Flex")
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package eu.darken.capod.pods.core.apple.beats
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class BeatsSolo3(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
@@ -15,12 +16,27 @@ data class BeatsSolo3(
|
||||
override val proximityMessage: ProximityPairing.Message
|
||||
) : BasicSingleApplePods {
|
||||
|
||||
override fun getLabel(context: Context): String = "Beats Solo 3"
|
||||
override val model: PodDevice.Model = PodDevice.Model.BEATS_SOLO_3
|
||||
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.ic_device_generic_earbuds
|
||||
class Factory @Inject constructor() : ApplePods.Factory(TAG) {
|
||||
|
||||
override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean =
|
||||
proximityMessage.getModelInfo().full == DEVICE_CODE
|
||||
|
||||
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
|
||||
val identifier = recognizeDevice(scanResult, proximityMessage)
|
||||
|
||||
return BeatsSolo3(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = proximityMessage,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEVICE_CODE = 0x0620.toUShort()
|
||||
private val DEVICE_CODE = 0x0620.toUShort()
|
||||
private val TAG = logTag("PodDevice", "Beats", "Solo", "3")
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package eu.darken.capod.pods.core.apple.beats
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class BeatsStudio3(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
@@ -15,12 +16,27 @@ data class BeatsStudio3(
|
||||
override val proximityMessage: ProximityPairing.Message
|
||||
) : BasicSingleApplePods {
|
||||
|
||||
override fun getLabel(context: Context): String = "Beats Studio 3"
|
||||
override val model: PodDevice.Model = PodDevice.Model.BEATS_STUDIO_3
|
||||
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.ic_device_generic_earbuds
|
||||
class Factory @Inject constructor() : ApplePods.Factory(TAG) {
|
||||
|
||||
override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean =
|
||||
proximityMessage.getModelInfo().dirty == DEVICE_CODE_DIRTY
|
||||
|
||||
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
|
||||
val identifier = recognizeDevice(scanResult, proximityMessage)
|
||||
|
||||
return BeatsStudio3(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = proximityMessage,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEVICE_CODE_DIRTY = 9.toUByte()
|
||||
private val DEVICE_CODE_DIRTY = 9.toUByte()
|
||||
private val TAG = logTag("PodDevice", "Beats", "Studio", "3")
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package eu.darken.capod.pods.core.apple.beats
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class BeatsX(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
@@ -15,12 +16,27 @@ data class BeatsX(
|
||||
override val proximityMessage: ProximityPairing.Message
|
||||
) : BasicSingleApplePods {
|
||||
|
||||
override fun getLabel(context: Context): String = "Beats X"
|
||||
override val model: PodDevice.Model = PodDevice.Model.BEATS_X
|
||||
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.ic_device_generic_earbuds
|
||||
class Factory @Inject constructor() : ApplePods.Factory(TAG) {
|
||||
|
||||
override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean =
|
||||
proximityMessage.getModelInfo().full == DEVICE_CODE
|
||||
|
||||
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
|
||||
val identifier = recognizeDevice(scanResult, proximityMessage)
|
||||
|
||||
return BeatsX(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = proximityMessage,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEVICE_CODE = 0x0520.toUShort()
|
||||
private val DEVICE_CODE = 0x0520.toUShort()
|
||||
private val TAG = logTag("PodDevice", "Beats", "X")
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package eu.darken.capod.pods.core.apple.beats
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class PowerBeats3(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
@@ -14,13 +15,26 @@ data class PowerBeats3(
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message
|
||||
) : BasicSingleApplePods {
|
||||
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_3
|
||||
|
||||
override fun getLabel(context: Context): String = "Power Beats 3"
|
||||
class Factory @Inject constructor() : ApplePods.Factory(TAG) {
|
||||
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.ic_device_generic_earbuds
|
||||
override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean =
|
||||
proximityMessage.getModelInfo().full == DEVICE_CODE
|
||||
|
||||
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
|
||||
val identifier = recognizeDevice(scanResult, proximityMessage)
|
||||
|
||||
return PowerBeats3(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = proximityMessage,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEVICE_CODE = 0x0320.toUShort()
|
||||
private val DEVICE_CODE = 0x0320.toUShort()
|
||||
private val TAG = logTag("PodDevice", "Beats", "PowerBeats", "3")
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package eu.darken.capod.pods.core.apple.beats
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class PowerBeatsPro(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
@@ -16,15 +17,37 @@ data class PowerBeatsPro(
|
||||
private val cachedBatteryPercentage: Float?,
|
||||
) : DualApplePods {
|
||||
|
||||
override fun getLabel(context: Context): String = "Power Beats Pro"
|
||||
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.ic_device_generic_earbuds
|
||||
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_PRO
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
class Factory @Inject constructor() : ApplePods.Factory(TAG) {
|
||||
|
||||
override fun isResponsible(proximityMessage: ProximityPairing.Message): Boolean =
|
||||
proximityMessage.getModelInfo().dirty == DEVICE_CODE_DIRTY
|
||||
|
||||
override fun create(scanResult: BleScanResult, proximityMessage: ProximityPairing.Message): ApplePods {
|
||||
val identifier = recognizeDevice(scanResult, proximityMessage)
|
||||
|
||||
val device = PowerBeatsPro(
|
||||
identifier = identifier,
|
||||
scanResult = scanResult,
|
||||
proximityMessage = proximityMessage,
|
||||
cachedBatteryPercentage = cachedValues[identifier]?.caseBatteryPercentage
|
||||
)
|
||||
|
||||
cachedValues[identifier] = ValueCache(
|
||||
caseBatteryPercentage = device.batteryCasePercent
|
||||
)
|
||||
|
||||
return device
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEVICE_CODE_DIRTY = 11.toUByte()
|
||||
private val DEVICE_CODE_DIRTY = 11.toUByte()
|
||||
private val TAG = logTag("PodDevice", "Beats", "PowerBeats", "Pro")
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,29 @@
|
||||
package eu.darken.capod.pods.core.apple
|
||||
|
||||
import dagger.Component
|
||||
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.apple.protocol.ContinuityProtocol
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkObject
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import testhelper.BaseTest
|
||||
import javax.inject.Singleton
|
||||
|
||||
abstract class BaseAirPodsTest : BaseTest() {
|
||||
@Singleton
|
||||
@Component(modules = [AppleFactoryModule::class])
|
||||
interface AppleFactoryTestComponent {
|
||||
|
||||
val appleFactory: AppleFactory
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(): AppleFactoryTestComponent
|
||||
}
|
||||
}
|
||||
|
||||
private val baseBleScanResult = BleScanResult(
|
||||
address = "77:49:4C:D8:25:0C",
|
||||
@@ -20,10 +32,7 @@ abstract class BaseAirPodsTest : BaseTest() {
|
||||
manufacturerSpecificData = emptyMap()
|
||||
)
|
||||
|
||||
val factory = AppleFactory(
|
||||
proximityPairingDecoder = ProximityPairing.Decoder(),
|
||||
continuityProtocolDecoder = ContinuityProtocol.Decoder(),
|
||||
)
|
||||
val factory: AppleFactory = DaggerBaseAirPodsTest_AppleFactoryTestComponent.factory().create().appleFactory
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
|
||||
Reference in New Issue
Block a user