Support for more pods + tests

This commit is contained in:
darken
2022-01-03 23:02:54 +01:00
parent e23304d8d7
commit 840c1c1a50
46 changed files with 615 additions and 398 deletions
@@ -0,0 +1,9 @@
package eu.darken.capod.common
import android.os.SystemClock
object SystemClockWrap {
val elapsedRealtimeNanos: Long
get() = SystemClock.elapsedRealtimeNanos()
}
@@ -10,7 +10,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.common.debug.logging.Logging.Priority.*
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
@@ -18,7 +18,7 @@ import eu.darken.capod.main.ui.overview.cards.pods.UnknownPodDeviceCardVH
import eu.darken.capod.monitor.core.PodMonitor
import eu.darken.capod.monitor.core.worker.MonitorControl
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.airpods.DualApplePods
import eu.darken.capod.pods.core.apple.DualApplePods
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import java.util.*
@@ -6,9 +6,9 @@ import eu.darken.capod.R
import eu.darken.capod.common.BuildConfigWrap
import eu.darken.capod.databinding.OverviewPodsAppleDualItemBinding
import eu.darken.capod.pods.core.DualPods
import eu.darken.capod.pods.core.airpods.DualApplePods
import eu.darken.capod.pods.core.airpods.DualApplePods.DeviceColor
import eu.darken.capod.pods.core.airpods.DualApplePods.LidState
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.DualApplePods.DeviceColor
import eu.darken.capod.pods.core.apple.DualApplePods.LidState
import eu.darken.capod.pods.core.getBatteryCase
import eu.darken.capod.pods.core.getBatteryLeftPod
import eu.darken.capod.pods.core.getBatteryRightPod
@@ -13,7 +13,7 @@ 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.logTag
import eu.darken.capod.monitor.core.worker.MonitorControl
import eu.darken.capod.pods.core.airpods.protocol.ContinuityProtocol
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -20,7 +20,7 @@ import eu.darken.capod.monitor.core.MonitorComponent
import eu.darken.capod.monitor.core.MonitorCoroutineScope
import eu.darken.capod.monitor.core.MonitorSettings
import eu.darken.capod.monitor.ui.MonitorNotifications
import eu.darken.capod.pods.core.airpods.protocol.ContinuityProtocol
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay
@@ -20,7 +20,7 @@ import eu.darken.capod.common.hasApiLevel
import eu.darken.capod.common.notifications.PendingIntentCompat
import eu.darken.capod.main.ui.MainActivity
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.airpods.DualApplePods
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.getBatteryCase
import eu.darken.capod.pods.core.getBatteryLeftPod
import eu.darken.capod.pods.core.getBatteryRightPod
@@ -53,7 +53,7 @@ class MonitorNotifications @Inject constructor(
.setChannelId(NOTIFICATION_CHANNEL_ID)
.setContentIntent(openPi)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setSmallIcon(R.drawable.ic_baseline_earbuds_24)
.setSmallIcon(R.drawable.ic_device_generic_earbuds)
.setOngoing(true)
.setContentTitle(context.getString(R.string.app_name))
}
@@ -61,7 +61,7 @@ class MonitorNotifications @Inject constructor(
fun getBuilder(device: PodDevice?): NotificationCompat.Builder {
if (device == null) {
builder.setContentTitle(context.getString(R.string.device_status_loading_message))
builder.setSmallIcon(R.drawable.ic_baseline_earbuds_24)
builder.setSmallIcon(R.drawable.ic_device_generic_earbuds)
return builder
}
@@ -28,5 +28,5 @@ interface PodDevice {
@get:DrawableRes
val iconRes: Int
get() = R.drawable.ic_baseline_earbuds_24
get() = R.drawable.ic_device_generic_earbuds
}
@@ -6,18 +6,18 @@ 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.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.airpods.AirPodsFactory
import eu.darken.capod.pods.core.apple.AppleFactory
import javax.inject.Inject
@Reusable
class PodFactory @Inject constructor(
private val airPodsFactory: AirPodsFactory
private val appleFactory: AppleFactory
) {
suspend fun createPod(scanResult: ScanResult): PodDevice? {
log(TAG, VERBOSE) { "Trying to create Pod for $scanResult" }
val pod = airPodsFactory.create(scanResult)
val pod = appleFactory.create(scanResult)
if (pod != null) {
log(TAG) { "Pod created: $pod" }
return pod
@@ -1,15 +0,0 @@
package eu.darken.capod.pods.core.airpods
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
interface ApplePods : PodDevice {
val proximityMessage: ProximityPairing.Message
companion object {
val TAG = logTag("Pod", "AppleDevice")
}
}
@@ -1,188 +0,0 @@
package eu.darken.capod.pods.core.airpods
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.isBitSet
import eu.darken.capod.common.lowerNibble
import eu.darken.capod.common.upperNibble
import eu.darken.capod.pods.core.DualPods
import eu.darken.capod.pods.core.DualPods.Pod
interface SingleApplePods : ApplePods, DualPods {
val tag: String
// We start counting at the airpods prefix byte
val rawPrefix: UByte
get() = proximityMessage.data[0]
val rawDeviceModel: UShort
get() = (((proximityMessage.data[1].toInt() and 255) shl 8) or (proximityMessage.data[2].toInt() and 255)).toUShort()
val rawStatus: UByte
get() = proximityMessage.data[3]
val rawPodsBattery: UByte
get() = proximityMessage.data[4]
val rawCaseBattery: UByte
get() = proximityMessage.data[5]
val rawCaseLidState: UByte
get() = proximityMessage.data[6]
val rawDeviceColor: UByte
get() = proximityMessage.data[7]
val rawSuffix: UByte
get() = proximityMessage.data[8]
override val microPhonePod: Pod
get() = when (rawStatus.isBitSet(5)) {
true -> Pod.LEFT
false -> Pod.RIGHT
}
override val batteryLeftPodPercent: Float?
get() {
val value = when (microPhonePod) {
Pod.LEFT -> rawPodsBattery.lowerNibble.toInt()
Pod.RIGHT -> rawPodsBattery.upperNibble.toInt()
}
return when (value) {
15 -> null
else -> if (value > 10) {
log(tag) { "Left pod: Above 100% battery: $value" }
1.0f
} else {
(value / 10f)
}
}
}
override val batteryRightPodPercent: Float?
get() {
val value = when (microPhonePod) {
Pod.LEFT -> rawPodsBattery.upperNibble.toInt()
Pod.RIGHT -> rawPodsBattery.lowerNibble.toInt()
}
return when (value) {
15 -> null
else -> if (value > 10) {
log(tag) { "Right pod: Above 100% battery: $value" }
1.0f
} else {
value / 10f
}
}
}
override val batteryCasePercent: Float?
get() = when (val value = rawCaseBattery.lowerNibble.toInt()) {
15 -> null
else -> if (value > 10) {
log(tag) { "Case: Above 100% battery: $value" }
1.0f
} else {
value / 10f
}
}
override val isLeftPodInEar: Boolean
get() = when (microPhonePod) {
Pod.LEFT -> rawStatus.isBitSet(1)
Pod.RIGHT -> rawStatus.isBitSet(3)
}
override val isRightPodInEar: Boolean
get() = when (microPhonePod) {
Pod.LEFT -> rawStatus.isBitSet(3)
Pod.RIGHT -> rawStatus.isBitSet(1)
}
val status: Status
get() = Status.values().firstOrNull { it.raw == rawStatus } ?: Status.UNKNOWN
enum class Status(val raw: UByte?) {
BOTH_AIRPODS_IN_CASE(0x55),
LEFT_IN_EAR(0x33),
AIRPLANE(0x02),
UNKNOWN(null);
constructor(raw: Int) : this(raw.toUByte())
}
// 1010101 0x55 85 Both In Case
// 0101011 0x2b 43 Both In Ear
// 0101001 0x29 41 Right in Ear
// 0100011 0x23 35 Left In Ear
// 0100001 0x21 33 Neither in Ear or In Case
// 1110001 0x71 113 Left in Case, Right on Desk
// 0010001 0x11 17 Left in Case, Right on Desk
// 0010011 0x13 19 Left in Case, Right in Ear
// 1110011 0x73 115 Left in Case, Right in Ear
override val isCaseCharging: Boolean
get() = rawCaseBattery.upperNibble.isBitSet(2)
override val isLeftPodCharging: Boolean
get() = when (microPhonePod) {
Pod.LEFT -> rawCaseBattery.upperNibble.isBitSet(0)
Pod.RIGHT -> rawCaseBattery.upperNibble.isBitSet(1)
}
override val isRightPodCharging: Boolean
get() = when (microPhonePod) {
Pod.LEFT -> rawCaseBattery.upperNibble.isBitSet(1)
Pod.RIGHT -> rawCaseBattery.upperNibble.isBitSet(0)
}
val caseLidState: LidState
get() = LidState.values().firstOrNull { it.raw == rawCaseLidState } ?: LidState.UNKNOWN
enum class LidState(val raw: UByte?) {
OPEN(0x31),
CLOSED(0x38),
NOT_IN_CASE(0x01),
UNKNOWN(null);
constructor(raw: Int) : this(raw.toUByte())
}
val deviceColor: DeviceColor
get() = DeviceColor.values().firstOrNull { it.raw == rawDeviceColor } ?: DeviceColor.UNKNOWN
enum class DeviceColor(val raw: UByte?) {
WHITE(0x00),
BLACK(0x01),
RED(0x02),
BLUE(0x03),
PINK(0x04),
GRAY(0x05),
SILVER(0x06),
GOLD(0x07),
ROSE_GOLD(0x08),
SPACE_GRAY(0x09),
DARK_BLUE(0x0a),
LIGHT_BLUE(0x0b),
YELLOW(0x0c),
UNKNOWN(null);
constructor(raw: Int) : this(raw.toUByte())
}
val connectionState: ConnectionState
get() = ConnectionState.values().firstOrNull { rawSuffix == it.raw } ?: ConnectionState.UNKNOWN
enum class ConnectionState(val raw: UByte?) {
DISCONNECTED(0x00),
IDLE(0x04),
MUSIC(0x05),
CALL(0x06),
RINGING(0x07),
HANGING_UP(0x09),
UNKNOWN(null);
constructor(raw: Int) : this(raw.toUByte())
}
}
@@ -1,21 +0,0 @@
package eu.darken.capod.pods.core.airpods.models
import android.bluetooth.le.ScanResult
import android.content.Context
import eu.darken.capod.pods.core.airpods.ApplePods
import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
import java.time.Instant
import java.util.*
data class AirPodsMax constructor(
override val identifier: UUID = UUID.randomUUID(),
override val lastSeenAt: Instant = Instant.now(),
override val scanResult: ScanResult,
override val proximityMessage: ProximityPairing.Message
) : ApplePods {
override fun getLabel(context: Context): String {
return "AirPods Max"
}
}
@@ -1,19 +1,17 @@
package eu.darken.capod.pods.core.airpods
package eu.darken.capod.pods.core.apple
import android.bluetooth.le.ScanResult
import android.os.SystemClock
import eu.darken.capod.common.SystemClockWrap
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.PodDevice
import eu.darken.capod.pods.core.airpods.models.AirPodsGen1
import eu.darken.capod.pods.core.airpods.models.AirPodsGen2
import eu.darken.capod.pods.core.airpods.models.AirPodsPro
import eu.darken.capod.pods.core.airpods.models.UnknownAppleDevice
import eu.darken.capod.pods.core.airpods.protocol.ContinuityProtocol
import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
import eu.darken.capod.pods.core.apple.airpods.*
import eu.darken.capod.pods.core.apple.beats.*
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
@@ -22,7 +20,7 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AirPodsFactory @Inject constructor(
class AppleFactory @Inject constructor(
private val continuityProtocolDecoder: ContinuityProtocol.Decoder,
private val proximityPairingDecoder: ProximityPairing.Decoder
) {
@@ -41,7 +39,7 @@ class AirPodsFactory @Inject constructor(
get() = Duration.ofNanos(scanResult.timestampNanos)
fun isOlderThan(age: Duration): Boolean {
val now = Duration.ofNanos(SystemClock.elapsedRealtimeNanos())
val now = Duration.ofNanos(SystemClockWrap.elapsedRealtimeNanos)
return now - timestampNanos > age
}
}
@@ -139,42 +137,57 @@ class AirPodsFactory @Inject constructor(
val dmDirty = pm.data[1]
return when {
dm == 0x0220.toUShort() || dmDirty == 2.toUByte() -> AirPodsGen1(
dm == 0x0220.toUShort() -> AirPodsGen1(
identifier = identifier,
scanResult = scanResult,
proximityMessage = pm
)
dm == 0x0F20.toUShort() || dmDirty == 15.toUByte() -> AirPodsGen2(
dm == 0x0F20.toUShort() -> AirPodsGen2(
identifier = identifier,
scanResult = scanResult,
proximityMessage = pm
)
dm == 0x0e20.toUShort() || dmDirty == 14.toUByte() -> AirPodsPro(
dm == 0x0e20.toUShort() -> AirPodsPro(
identifier = identifier,
scanResult = scanResult,
proximityMessage = pm
)
// dmDirty == 10.toUByte() -> {
// TODO("Airpods Max")
// }
// dmDirty == 11.toUByte() -> {
// TODO("PowerBeatsPro")
// }
// dm == 0x0520.toUShort() || dmDirty == 5.toUByte() -> {
// TODO("BeatsX")
// }
// dmDirty == 0.toUByte() -> {
// TODO("BeatsFlex")
// }
// dm == 0x0620.toUShort() || dmDirty == 6.toUByte() -> {
// TODO("Beats Solo 3")
// }
// dmDirty == 9.toUByte() -> {
// TODO("Beats Studio 3")
// }
// dm == 0x0320.toUShort() || dmDirty == 3.toUByte() -> {
// TODO("Power Beats 3")
// }
dmDirty == 10.toUByte() -> AirPodsMax(
identifier = identifier,
scanResult = scanResult,
proximityMessage = pm
)
dm == 0x0320.toUShort() -> PowerBeats3(
identifier = identifier,
scanResult = scanResult,
proximityMessage = pm
)
dm == 0x0620.toUShort() -> BeatsSolo3(
identifier = identifier,
scanResult = scanResult,
proximityMessage = pm
)
dmDirty == 9.toUByte() -> BeatsStudio3(
identifier = identifier,
scanResult = scanResult,
proximityMessage = pm
)
dmDirty == 11.toUByte() -> PowerBeatsPro(
identifier = identifier,
scanResult = scanResult,
proximityMessage = pm
)
dm == 0x0520.toUShort() || dmDirty == 5.toUByte() -> BeatsX(
identifier = identifier,
scanResult = scanResult,
proximityMessage = pm
)
dm == 0x1020.toUShort() -> BeatsFlex(
identifier = identifier,
scanResult = scanResult,
proximityMessage = pm
)
else -> {
log(TAG, WARN) { "Unknown proximity message type" }
Bugs.report(IllegalArgumentException("Unknown ProximityMessage: $pm"))
@@ -0,0 +1,42 @@
package eu.darken.capod.pods.core.apple
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
interface ApplePods : PodDevice {
val tag: String
val proximityMessage: ProximityPairing.Message
// We start counting at the airpods prefix byte
val rawPrefix: UByte
get() = proximityMessage.data[0]
val rawDeviceModel: UShort
get() = (((proximityMessage.data[1].toInt() and 255) shl 8) or (proximityMessage.data[2].toInt() and 255)).toUShort()
val rawStatus: UByte
get() = proximityMessage.data[3]
val rawPodsBattery: UByte
get() = proximityMessage.data[4]
val rawCaseBattery: UByte
get() = proximityMessage.data[5]
val rawCaseLidState: UByte
get() = proximityMessage.data[6]
val rawDeviceColor: UByte
get() = proximityMessage.data[7]
val rawSuffix: UByte
get() = proximityMessage.data[8]
companion object {
val TAG = logTag("Pod", "AppleDevice")
}
}
@@ -0,0 +1,18 @@
package eu.darken.capod.pods.core.apple
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.lowerNibble
interface BasicSingleApplePods : ApplePods {
val batteryHeadsetPercent: Float?
get() = when (val value = rawPodsBattery.lowerNibble.toInt()) {
15 -> null
else -> if (value > 10) {
log(tag) { "Left pod: Above 100% battery: $value" }
1.0f
} else {
(value / 10f)
}
}
}
@@ -1,4 +1,4 @@
package eu.darken.capod.pods.core.airpods
package eu.darken.capod.pods.core.apple
import android.content.Context
import androidx.annotation.StringRes
@@ -12,33 +12,6 @@ import eu.darken.capod.pods.core.DualPods.Pod
interface DualApplePods : ApplePods, DualPods {
val tag: String
// We start counting at the airpods prefix byte
val rawPrefix: UByte
get() = proximityMessage.data[0]
val rawDeviceModel: UShort
get() = (((proximityMessage.data[1].toInt() and 255) shl 8) or (proximityMessage.data[2].toInt() and 255)).toUShort()
val rawStatus: UByte
get() = proximityMessage.data[3]
val rawPodsBattery: UByte
get() = proximityMessage.data[4]
val rawCaseBattery: UByte
get() = proximityMessage.data[5]
val rawCaseLidState: UByte
get() = proximityMessage.data[6]
val rawDeviceColor: UByte
get() = proximityMessage.data[7]
val rawSuffix: UByte
get() = proximityMessage.data[8]
override val microPhonePod: Pod
get() = when (rawStatus.isBitSet(5)) {
true -> Pod.LEFT
@@ -0,0 +1,29 @@
package eu.darken.capod.pods.core.apple
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.isBitSet
import eu.darken.capod.common.lowerNibble
import eu.darken.capod.common.upperNibble
interface SingleApplePods : BasicSingleApplePods {
val batteryCasePercent: Float?
get() = when (val value = rawCaseBattery.lowerNibble.toInt()) {
15 -> null
else -> if (value > 10) {
log(tag) { "Case: Above 100% battery: $value" }
1.0f
} else {
value / 10f
}
}
val isHeadsetBeingWorn: Boolean
get() = rawStatus.isBitSet(1)
val isCaseCharging: Boolean
get() = rawCaseBattery.upperNibble.isBitSet(2)
val isHeadsetBeingCharged: Boolean
get() = rawCaseBattery.upperNibble.isBitSet(0)
}
@@ -1,9 +1,10 @@
package eu.darken.capod.pods.core.airpods.models
package eu.darken.capod.pods.core.apple
import android.bluetooth.le.ScanResult
import android.content.Context
import eu.darken.capod.pods.core.airpods.ApplePods
import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
import eu.darken.capod.R
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import java.util.*
@@ -15,7 +16,8 @@ data class UnknownAppleDevice constructor(
) : ApplePods {
override fun getLabel(context: Context): String {
return "Unknown device"
return context.getString(R.string.device_unknown_label)
}
override val tag: String = logTag("Pod", "Apple", "Unknown")
}
@@ -1,11 +1,11 @@
package eu.darken.capod.pods.core.airpods.models
package eu.darken.capod.pods.core.apple.airpods
import android.bluetooth.le.ScanResult
import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.airpods.DualApplePods
import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import java.util.*
@@ -16,12 +16,10 @@ data class AirPodsGen1 constructor(
override val proximityMessage: ProximityPairing.Message
) : DualApplePods {
override fun getLabel(context: Context): String {
return "AirPods (Gen 1)"
}
override fun getLabel(context: Context): String = "AirPods (Gen 1)"
override val iconRes: Int
get() = R.drawable.ic_device_airpods_gen1
override val tag: String = logTag("Pod", "AirPodsGen1")
override val tag: String = logTag("Pod", "Apple", "AirPods", "Gen1")
}
@@ -1,11 +1,11 @@
package eu.darken.capod.pods.core.airpods.models
package eu.darken.capod.pods.core.apple.airpods
import android.bluetooth.le.ScanResult
import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.airpods.DualApplePods
import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import java.util.*
@@ -16,12 +16,10 @@ data class AirPodsGen2 constructor(
override val proximityMessage: ProximityPairing.Message
) : DualApplePods {
override fun getLabel(context: Context): String {
return "AirPods (Gen 2)"
}
override fun getLabel(context: Context): String = "AirPods (Gen 2)"
override val iconRes: Int
get() = R.drawable.ic_device_airpods_gen2
override val tag: String = logTag("Pod", "AirPodsGen2")
override val tag: String = logTag("Pod", "Apple", "AirPods", "Gen2")
}
@@ -0,0 +1,25 @@
package eu.darken.capod.pods.core.apple.airpods
import android.bluetooth.le.ScanResult
import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.SingleApplePods
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import java.util.*
data class AirPodsMax constructor(
override val identifier: UUID = UUID.randomUUID(),
override val lastSeenAt: Instant = Instant.now(),
override val scanResult: ScanResult,
override val proximityMessage: ProximityPairing.Message
) : SingleApplePods {
override fun getLabel(context: Context): String = "AirPods Max"
override val iconRes: Int
get() = R.drawable.ic_device_generic_headphones
override val tag: String = logTag("Pod", "Apple", "AirPods", "Max")
}
@@ -1,10 +1,10 @@
package eu.darken.capod.pods.core.airpods.models
package eu.darken.capod.pods.core.apple.airpods
import android.bluetooth.le.ScanResult
import android.content.Context
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.airpods.DualApplePods
import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import java.util.*
@@ -19,5 +19,5 @@ data class AirPodsPro constructor(
return "AirPods Pro"
}
override val tag: String = logTag("Pod", "AirPodsPro")
override val tag: String = logTag("Pod", "Apple", "AirPods", "Pro")
}
@@ -0,0 +1,25 @@
package eu.darken.capod.pods.core.apple.beats
import android.bluetooth.le.ScanResult
import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import java.util.*
data class BeatsFlex constructor(
override val identifier: UUID = UUID.randomUUID(),
override val lastSeenAt: Instant = Instant.now(),
override val scanResult: ScanResult,
override val proximityMessage: ProximityPairing.Message
) : BasicSingleApplePods {
override fun getLabel(context: Context): String = "Beats Flex"
override val iconRes: Int
get() = R.drawable.ic_device_generic_earbuds
override val tag: String = logTag("Pod", "Apple", "Beats", "Flex")
}
@@ -0,0 +1,25 @@
package eu.darken.capod.pods.core.apple.beats
import android.bluetooth.le.ScanResult
import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import java.util.*
data class BeatsSolo3 constructor(
override val identifier: UUID = UUID.randomUUID(),
override val lastSeenAt: Instant = Instant.now(),
override val scanResult: ScanResult,
override val proximityMessage: ProximityPairing.Message
) : BasicSingleApplePods {
override fun getLabel(context: Context): String = "Beats Solo 3"
override val iconRes: Int
get() = R.drawable.ic_device_generic_earbuds
override val tag: String = logTag("Pod", "Apple", "Beats", "Solo3")
}
@@ -0,0 +1,25 @@
package eu.darken.capod.pods.core.apple.beats
import android.bluetooth.le.ScanResult
import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import java.util.*
data class BeatsStudio3 constructor(
override val identifier: UUID = UUID.randomUUID(),
override val lastSeenAt: Instant = Instant.now(),
override val scanResult: ScanResult,
override val proximityMessage: ProximityPairing.Message
) : BasicSingleApplePods {
override fun getLabel(context: Context): String = "Beats Studio 3"
override val iconRes: Int
get() = R.drawable.ic_device_generic_earbuds
override val tag: String = logTag("Pod", "Apple", "Beats", "Studio3")
}
@@ -0,0 +1,25 @@
package eu.darken.capod.pods.core.apple.beats
import android.bluetooth.le.ScanResult
import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import java.util.*
data class BeatsX constructor(
override val identifier: UUID = UUID.randomUUID(),
override val lastSeenAt: Instant = Instant.now(),
override val scanResult: ScanResult,
override val proximityMessage: ProximityPairing.Message
) : BasicSingleApplePods {
override fun getLabel(context: Context): String = "Beats X"
override val iconRes: Int
get() = R.drawable.ic_device_generic_earbuds
override val tag: String = logTag("Pod", "Apple", "Beats", "X")
}
@@ -0,0 +1,25 @@
package eu.darken.capod.pods.core.apple.beats
import android.bluetooth.le.ScanResult
import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import java.util.*
data class PowerBeats3 constructor(
override val identifier: UUID = UUID.randomUUID(),
override val lastSeenAt: Instant = Instant.now(),
override val scanResult: ScanResult,
override val proximityMessage: ProximityPairing.Message
) : BasicSingleApplePods {
override fun getLabel(context: Context): String = "Power Beats 3"
override val iconRes: Int
get() = R.drawable.ic_device_generic_earbuds
override val tag: String = logTag("Pod", "Apple", "Beats", "Power3")
}
@@ -0,0 +1,25 @@
package eu.darken.capod.pods.core.apple.beats
import android.bluetooth.le.ScanResult
import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import java.util.*
data class PowerBeatsPro constructor(
override val identifier: UUID = UUID.randomUUID(),
override val lastSeenAt: Instant = Instant.now(),
override val scanResult: ScanResult,
override val proximityMessage: ProximityPairing.Message
) : DualApplePods {
override fun getLabel(context: Context): String = "Power Beats Pro"
override val iconRes: Int
get() = R.drawable.ic_device_generic_earbuds
override val tag: String = logTag("Pod", "Apple", "Beats", "PowerPro")
}
@@ -1,4 +1,4 @@
package eu.darken.capod.pods.core.airpods.protocol
package eu.darken.capod.pods.core.apple.protocol
import android.bluetooth.le.ScanResult
import android.os.ParcelUuid
@@ -1,9 +1,9 @@
package eu.darken.capod.pods.core.airpods.protocol
package eu.darken.capod.pods.core.apple.protocol
import android.bluetooth.le.ScanFilter
import dagger.Reusable
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.pods.core.airpods.ApplePods
import eu.darken.capod.pods.core.apple.ApplePods
import javax.inject.Inject
@@ -0,0 +1,10 @@
<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="M12,3c-4.97,0 -9,4.03 -9,9v7c0,1.1 0.9,2 2,2h4v-8H5v-1c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v1h-4v8h4c1.1,0 2,-0.9 2,-2v-7C21,7.03 16.97,3 12,3z" />
</vector>
@@ -22,7 +22,7 @@
app:layout_constraintBottom_toBottomOf="@id/name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/name"
app:srcCompat="@drawable/ic_baseline_earbuds_24" />
app:srcCompat="@drawable/ic_device_generic_earbuds" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/name"
+1
View File
@@ -81,4 +81,5 @@
<string name="settings_support_email_developer_description">Note that I can only respond in german or english.</string>
<string name="settings_debug_autoreports_label">Automatic bug reports</string>
<string name="settings_debug_autoreports_description">Automatically reports issues, e.g. details on an app crash so I can figure out how to fix it.</string>
<string name="device_unknown_label">Unknown device</string>
</resources>