Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
653c7b8c4b | ||
|
|
4e38bcd4cc | ||
|
|
fcbeda4888 | ||
|
|
4101fddc5b | ||
|
|
86df7bea22 | ||
|
|
4db8c0c286 | ||
|
|
1d03335903 | ||
|
|
b1b541014f | ||
|
|
b40a7e435f | ||
|
|
8305fb4c5f |
@@ -108,13 +108,17 @@ interface PodDevice {
|
|||||||
"AirPods (Gen 1)? \uD83C\uDFAD",
|
"AirPods (Gen 1)? \uD83C\uDFAD",
|
||||||
R.drawable.devic_airpods_gen1_both,
|
R.drawable.devic_airpods_gen1_both,
|
||||||
),
|
),
|
||||||
|
@Json(name = "fakes.generic.airpods.gen3") FAKE_AIRPODS_GEN3(
|
||||||
|
"AirPods (Gen 3)? \uD83C\uDFAD",
|
||||||
|
R.drawable.devic_airpods_gen2_both,
|
||||||
|
),
|
||||||
@Json(name = "fakes.varunr.airpodspro") FAKE_AIRPODS_PRO(
|
@Json(name = "fakes.varunr.airpodspro") FAKE_AIRPODS_PRO(
|
||||||
"AirPods Pro? \uD83C\uDFAD",
|
"AirPods Pro? \uD83C\uDFAD",
|
||||||
R.drawable.devic_airpods_pro2_both,
|
R.drawable.devic_airpods_pro2_both,
|
||||||
),
|
),
|
||||||
@Json(name = "fakes.generic.airpods.gen3") FAKE_AIRPODS_GEN3(
|
@Json(name = "fakes.generic.airpods.pro2") FAKE_AIRPODS_PRO2(
|
||||||
"AirPods (Gen 3)? \uD83C\uDFAD",
|
"AirPods Pro2? \uD83C\uDFAD",
|
||||||
R.drawable.devic_airpods_gen2_both,
|
R.drawable.devic_airpods_pro2_both,
|
||||||
),
|
),
|
||||||
@Json(name = "unknown") UNKNOWN(
|
@Json(name = "unknown") UNKNOWN(
|
||||||
"Unknown"
|
"Unknown"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import eu.darken.capod.pods.core.apple.beats.*
|
|||||||
import eu.darken.capod.pods.core.apple.misc.FakeAirPodsGen1
|
import eu.darken.capod.pods.core.apple.misc.FakeAirPodsGen1
|
||||||
import eu.darken.capod.pods.core.apple.misc.FakeAirPodsGen3
|
import eu.darken.capod.pods.core.apple.misc.FakeAirPodsGen3
|
||||||
import eu.darken.capod.pods.core.apple.misc.FakeAirPodsPro
|
import eu.darken.capod.pods.core.apple.misc.FakeAirPodsPro
|
||||||
|
import eu.darken.capod.pods.core.apple.misc.FakeAirPodsPro2
|
||||||
|
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
@Module
|
@Module
|
||||||
@@ -31,6 +32,7 @@ abstract class AppleFactoryModule {
|
|||||||
@Binds @IntoSet abstract fun powerBeatsPro(factory: PowerBeatsPro.Factory): ApplePodsFactory<out ApplePods>
|
@Binds @IntoSet abstract fun powerBeatsPro(factory: PowerBeatsPro.Factory): ApplePodsFactory<out ApplePods>
|
||||||
|
|
||||||
@Binds @IntoSet abstract fun fakeAirPodsGen1(factory: FakeAirPodsGen1.Factory): ApplePodsFactory<out ApplePods>
|
@Binds @IntoSet abstract fun fakeAirPodsGen1(factory: FakeAirPodsGen1.Factory): ApplePodsFactory<out ApplePods>
|
||||||
@Binds @IntoSet abstract fun fakeAirPodsPro(factory: FakeAirPodsPro.Factory): ApplePodsFactory<out ApplePods>
|
|
||||||
@Binds @IntoSet abstract fun fakeAirPodsGen3(factory: FakeAirPodsGen3.Factory): ApplePodsFactory<out ApplePods>
|
@Binds @IntoSet abstract fun fakeAirPodsGen3(factory: FakeAirPodsGen3.Factory): ApplePodsFactory<out ApplePods>
|
||||||
|
@Binds @IntoSet abstract fun fakeAirPodsPro(factory: FakeAirPodsPro.Factory): ApplePodsFactory<out ApplePods>
|
||||||
|
@Binds @IntoSet abstract fun fakeAirPodsPro2(factory: FakeAirPodsPro2.Factory): ApplePodsFactory<out ApplePods>
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package eu.darken.capod.pods.core.apple.airpods
|
package eu.darken.capod.pods.core.apple.airpods
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import eu.darken.capod.common.R
|
||||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
@@ -26,6 +28,22 @@ data class AirPodsPro2(
|
|||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_PRO2
|
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_PRO2
|
||||||
|
|
||||||
|
@get:DrawableRes
|
||||||
|
override val iconRes: Int
|
||||||
|
get() = R.drawable.devic_airpods_pro2_both
|
||||||
|
|
||||||
|
@get:DrawableRes
|
||||||
|
override val caseIcon: Int
|
||||||
|
get() = R.drawable.devic_airpods_pro2_case
|
||||||
|
|
||||||
|
@get:DrawableRes
|
||||||
|
override val leftPodIcon: Int
|
||||||
|
get() = R.drawable.devic_airpods_pro2_left
|
||||||
|
|
||||||
|
@get:DrawableRes
|
||||||
|
override val rightPodIcon: Int
|
||||||
|
get() = R.drawable.devic_airpods_pro2_right
|
||||||
|
|
||||||
override val batteryCasePercent: Float?
|
override val batteryCasePercent: Float?
|
||||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import eu.darken.capod.common.debug.logging.logTag
|
|||||||
import eu.darken.capod.common.isBitSet
|
import eu.darken.capod.common.isBitSet
|
||||||
import eu.darken.capod.common.lowerNibble
|
import eu.darken.capod.common.lowerNibble
|
||||||
import eu.darken.capod.common.upperNibble
|
import eu.darken.capod.common.upperNibble
|
||||||
import eu.darken.capod.pods.core.DualPodDevice
|
import eu.darken.capod.pods.core.*
|
||||||
import eu.darken.capod.pods.core.HasCase
|
|
||||||
import eu.darken.capod.pods.core.HasDualMicrophone
|
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
|
||||||
import eu.darken.capod.pods.core.apple.ApplePods
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
@@ -31,7 +28,7 @@ data class FakeAirPodsGen1 constructor(
|
|||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
private val cachedBatteryPercentage: Float? = null,
|
private val cachedBatteryPercentage: Float? = null,
|
||||||
) : ApplePods, DualPodDevice, HasDualMicrophone, HasCase {
|
) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN1
|
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN1
|
||||||
|
|
||||||
@@ -79,22 +76,32 @@ data class FakeAirPodsGen1 constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isThisPodInThecase: Boolean
|
private val isThisPodInThecase: Boolean
|
||||||
get() = rawStatus.isBitSet(6)
|
get() = rawStatus.isBitSet(6)
|
||||||
|
|
||||||
/**
|
override val isLeftPodInEar: Boolean
|
||||||
* The data flip bit is set if the left pod is primary.
|
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||||
* For the pod that is in the case, this is flipped again though.
|
true -> rawStatus.isBitSet(3)
|
||||||
*/
|
false -> rawStatus.isBitSet(1)
|
||||||
override val isLeftPodMicrophone: Boolean
|
}
|
||||||
get() = rawStatus.isBitSet(5) xor isThisPodInThecase
|
|
||||||
|
|
||||||
/**
|
override val isRightPodInEar: Boolean
|
||||||
* The data flip bit is UNset if the right pod is primary.
|
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||||
* For the pod that is in the case, this is flipped again though.
|
true -> rawStatus.isBitSet(1)
|
||||||
*/
|
false -> rawStatus.isBitSet(3)
|
||||||
override val isRightPodMicrophone: Boolean
|
}
|
||||||
get() = !rawStatus.isBitSet(5) xor isThisPodInThecase
|
|
||||||
|
override val isLeftPodCharging: Boolean
|
||||||
|
get() = when (areValuesFlipped) {
|
||||||
|
false -> rawFlags.isBitSet(0)
|
||||||
|
true -> rawFlags.isBitSet(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isRightPodCharging: Boolean
|
||||||
|
get() = when (areValuesFlipped) {
|
||||||
|
false -> rawFlags.isBitSet(1)
|
||||||
|
true -> rawFlags.isBitSet(0)
|
||||||
|
}
|
||||||
|
|
||||||
override val batteryCasePercent: Float?
|
override val batteryCasePercent: Float?
|
||||||
get() = when (val value = rawCaseBattery.toInt()) {
|
get() = when (val value = rawCaseBattery.toInt()) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ data class FakeAirPodsGen3 constructor(
|
|||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
private val cachedBatteryPercentage: Float? = null,
|
private val cachedBatteryPercentage: Float? = null,
|
||||||
) : ApplePods, DualPodDevice, HasDualMicrophone, HasCase, HasChargeDetectionDual {
|
) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN3
|
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN3
|
||||||
|
|
||||||
@@ -88,22 +88,20 @@ data class FakeAirPodsGen3 constructor(
|
|||||||
true -> rawFlags.isBitSet(0)
|
true -> rawFlags.isBitSet(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isThisPodInThecase: Boolean
|
private val isThisPodInThecase: Boolean
|
||||||
get() = rawStatus.isBitSet(6)
|
get() = rawStatus.isBitSet(6)
|
||||||
|
|
||||||
/**
|
override val isLeftPodInEar: Boolean
|
||||||
* The data flip bit is set if the left pod is primary.
|
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||||
* For the pod that is in the case, this is flipped again though.
|
true -> rawStatus.isBitSet(3)
|
||||||
*/
|
false -> rawStatus.isBitSet(1)
|
||||||
override val isLeftPodMicrophone: Boolean
|
}
|
||||||
get() = rawStatus.isBitSet(5) xor isThisPodInThecase
|
|
||||||
|
|
||||||
/**
|
override val isRightPodInEar: Boolean
|
||||||
* The data flip bit is UNset if the right pod is primary.
|
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||||
* For the pod that is in the case, this is flipped again though.
|
true -> rawStatus.isBitSet(1)
|
||||||
*/
|
false -> rawStatus.isBitSet(3)
|
||||||
override val isRightPodMicrophone: Boolean
|
}
|
||||||
get() = !rawStatus.isBitSet(5) xor isThisPodInThecase
|
|
||||||
|
|
||||||
override val batteryCasePercent: Float?
|
override val batteryCasePercent: Float?
|
||||||
get() = when (val value = rawCaseBattery.toInt()) {
|
get() = when (val value = rawCaseBattery.toInt()) {
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
package eu.darken.capod.pods.core.apple.misc
|
package eu.darken.capod.pods.core.apple.misc
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import eu.darken.capod.common.R
|
||||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
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
|
||||||
import eu.darken.capod.common.isBitSet
|
import eu.darken.capod.common.isBitSet
|
||||||
import eu.darken.capod.common.lowerNibble
|
import eu.darken.capod.common.lowerNibble
|
||||||
import eu.darken.capod.common.upperNibble
|
import eu.darken.capod.common.upperNibble
|
||||||
import eu.darken.capod.pods.core.DualPodDevice
|
import eu.darken.capod.pods.core.*
|
||||||
import eu.darken.capod.pods.core.HasCase
|
|
||||||
import eu.darken.capod.pods.core.HasDualMicrophone
|
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
|
||||||
import eu.darken.capod.pods.core.apple.ApplePods
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
@@ -31,10 +30,26 @@ data class FakeAirPodsPro constructor(
|
|||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
private val cachedBatteryPercentage: Float? = null,
|
private val cachedBatteryPercentage: Float? = null,
|
||||||
) : ApplePods, DualPodDevice, HasDualMicrophone, HasCase {
|
) : ApplePods, DualPodDevice, HasChargeDetectionDual, HasEarDetectionDual, HasCase {
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_PRO
|
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_PRO
|
||||||
|
|
||||||
|
@get:DrawableRes
|
||||||
|
override val iconRes: Int
|
||||||
|
get() = R.drawable.devic_airpods_pro2_both
|
||||||
|
|
||||||
|
@get:DrawableRes
|
||||||
|
override val caseIcon: Int
|
||||||
|
get() = R.drawable.devic_airpods_pro2_case
|
||||||
|
|
||||||
|
@get:DrawableRes
|
||||||
|
override val leftPodIcon: Int
|
||||||
|
get() = R.drawable.devic_airpods_pro2_left
|
||||||
|
|
||||||
|
@get:DrawableRes
|
||||||
|
override val rightPodIcon: Int
|
||||||
|
get() = R.drawable.devic_airpods_pro2_right
|
||||||
|
|
||||||
override val rssi: Int
|
override val rssi: Int
|
||||||
get() = rssiAverage ?: super<ApplePods>.rssi
|
get() = rssiAverage ?: super<ApplePods>.rssi
|
||||||
|
|
||||||
@@ -79,22 +94,32 @@ data class FakeAirPodsPro constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isThisPodInThecase: Boolean
|
override val isLeftPodCharging: Boolean
|
||||||
|
get() = when (areValuesFlipped) {
|
||||||
|
false -> rawFlags.isBitSet(0)
|
||||||
|
true -> rawFlags.isBitSet(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isRightPodCharging: Boolean
|
||||||
|
get() = when (areValuesFlipped) {
|
||||||
|
false -> rawFlags.isBitSet(1)
|
||||||
|
true -> rawFlags.isBitSet(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val isThisPodInThecase: Boolean
|
||||||
get() = rawStatus.isBitSet(6)
|
get() = rawStatus.isBitSet(6)
|
||||||
|
|
||||||
/**
|
override val isLeftPodInEar: Boolean
|
||||||
* The data flip bit is set if the left pod is primary.
|
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||||
* For the pod that is in the case, this is flipped again though.
|
true -> rawStatus.isBitSet(3)
|
||||||
*/
|
false -> rawStatus.isBitSet(1)
|
||||||
override val isLeftPodMicrophone: Boolean
|
}
|
||||||
get() = rawStatus.isBitSet(5) xor isThisPodInThecase
|
|
||||||
|
|
||||||
/**
|
override val isRightPodInEar: Boolean
|
||||||
* The data flip bit is UNset if the right pod is primary.
|
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||||
* For the pod that is in the case, this is flipped again though.
|
true -> rawStatus.isBitSet(1)
|
||||||
*/
|
false -> rawStatus.isBitSet(3)
|
||||||
override val isRightPodMicrophone: Boolean
|
}
|
||||||
get() = !rawStatus.isBitSet(5) xor isThisPodInThecase
|
|
||||||
|
|
||||||
override val batteryCasePercent: Float?
|
override val batteryCasePercent: Float?
|
||||||
get() = when (val value = rawCaseBattery.toInt()) {
|
get() = when (val value = rawCaseBattery.toInt()) {
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package eu.darken.capod.pods.core.apple.misc
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import eu.darken.capod.common.R
|
||||||
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
|
import eu.darken.capod.common.debug.logging.log
|
||||||
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
|
import eu.darken.capod.common.isBitSet
|
||||||
|
import eu.darken.capod.common.lowerNibble
|
||||||
|
import eu.darken.capod.common.upperNibble
|
||||||
|
import eu.darken.capod.pods.core.*
|
||||||
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
|
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||||
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
|
import java.time.Instant
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AirPods Pro2 clone
|
||||||
|
* https://discord.com/channels/548521543039189022/927235844127993866/1060911213539774504
|
||||||
|
*/
|
||||||
|
data class FakeAirPodsPro2 constructor(
|
||||||
|
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||||
|
override val seenLastAt: Instant = Instant.now(),
|
||||||
|
override val seenFirstAt: Instant = Instant.now(),
|
||||||
|
override val seenCounter: Int = 1,
|
||||||
|
override val scanResult: BleScanResult,
|
||||||
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
|
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
|
private val rssiAverage: Int? = null,
|
||||||
|
private val cachedBatteryPercentage: Float? = null,
|
||||||
|
private val cachedCaseState: DualAirPods.LidState? = null,
|
||||||
|
) : ApplePods, HasChargeDetectionDual, DualPodDevice, HasEarDetectionDual, HasCase {
|
||||||
|
|
||||||
|
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_PRO2
|
||||||
|
|
||||||
|
@get:DrawableRes
|
||||||
|
override val iconRes: Int
|
||||||
|
get() = R.drawable.devic_airpods_pro2_both
|
||||||
|
|
||||||
|
@get:DrawableRes
|
||||||
|
override val caseIcon: Int
|
||||||
|
get() = R.drawable.devic_airpods_pro2_case
|
||||||
|
|
||||||
|
@get:DrawableRes
|
||||||
|
override val leftPodIcon: Int
|
||||||
|
get() = R.drawable.devic_airpods_pro2_left
|
||||||
|
|
||||||
|
@get:DrawableRes
|
||||||
|
override val rightPodIcon: Int
|
||||||
|
get() = R.drawable.devic_airpods_pro2_right
|
||||||
|
|
||||||
|
override val rssi: Int
|
||||||
|
get() = rssiAverage ?: scanResult.rssi
|
||||||
|
|
||||||
|
private val areValuesFlipped: Boolean
|
||||||
|
get() = !rawStatus.isBitSet(5)
|
||||||
|
|
||||||
|
override val batteryLeftPodPercent: Float?
|
||||||
|
get() {
|
||||||
|
val value = when (areValuesFlipped) {
|
||||||
|
true -> rawPodsBattery.upperNibble.toInt()
|
||||||
|
false -> rawPodsBattery.lowerNibble.toInt()
|
||||||
|
}
|
||||||
|
return when (value) {
|
||||||
|
15 -> null
|
||||||
|
else -> if (value > 10) {
|
||||||
|
log { "Left pod: Above 100% battery: $value" }
|
||||||
|
1.0f
|
||||||
|
} else {
|
||||||
|
(value / 10f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val batteryRightPodPercent: Float?
|
||||||
|
get() {
|
||||||
|
val value = when (areValuesFlipped) {
|
||||||
|
true -> rawPodsBattery.lowerNibble.toInt()
|
||||||
|
false -> rawPodsBattery.upperNibble.toInt()
|
||||||
|
}
|
||||||
|
return when (value) {
|
||||||
|
15 -> null
|
||||||
|
else -> if (value > 10) {
|
||||||
|
log { "Right pod: Above 100% battery: $value" }
|
||||||
|
1.0f
|
||||||
|
} else {
|
||||||
|
value / 10f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val isThisPodInThecase: Boolean
|
||||||
|
get() = rawStatus.isBitSet(6)
|
||||||
|
|
||||||
|
override val isLeftPodInEar: Boolean
|
||||||
|
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||||
|
true -> rawStatus.isBitSet(3)
|
||||||
|
false -> rawStatus.isBitSet(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isRightPodInEar: Boolean
|
||||||
|
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||||
|
true -> rawStatus.isBitSet(1)
|
||||||
|
false -> rawStatus.isBitSet(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isLeftPodCharging: Boolean
|
||||||
|
get() = when (areValuesFlipped) {
|
||||||
|
false -> rawFlags.isBitSet(0)
|
||||||
|
true -> rawFlags.isBitSet(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isRightPodCharging: Boolean
|
||||||
|
get() = when (areValuesFlipped) {
|
||||||
|
false -> rawFlags.isBitSet(1)
|
||||||
|
true -> rawFlags.isBitSet(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val batteryCasePercent: Float?
|
||||||
|
get() = when (val value = rawCaseBattery.toInt()) {
|
||||||
|
15 -> cachedBatteryPercentage
|
||||||
|
else -> if (value > 10) {
|
||||||
|
log { "Case: Above 100% battery: $value" }
|
||||||
|
1.0f
|
||||||
|
} else {
|
||||||
|
value / 10f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isCaseCharging: Boolean
|
||||||
|
get() = rawFlags.isBitSet(2)
|
||||||
|
|
||||||
|
class Factory @Inject constructor() : ApplePodsFactory<FakeAirPodsPro2>(TAG) {
|
||||||
|
|
||||||
|
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||||
|
// Official message length is 19HEX, i.e. binary 25, did they copy this wrong?
|
||||||
|
getModelInfo().full == DEVICE_CODE && length == 19
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||||
|
var basic = FakeAirPodsPro2(scanResult = scanResult, proximityMessage = message)
|
||||||
|
val result = searchHistory(basic)
|
||||||
|
|
||||||
|
if (result != null) basic = basic.copy(identifier = result.id)
|
||||||
|
updateHistory(basic)
|
||||||
|
|
||||||
|
if (result == null) return basic
|
||||||
|
|
||||||
|
return basic.copy(
|
||||||
|
identifier = result.id,
|
||||||
|
seenFirstAt = result.seenFirstAt,
|
||||||
|
seenLastAt = scanResult.receivedAt,
|
||||||
|
seenCounter = result.seenCounter,
|
||||||
|
confidence = result.confidence,
|
||||||
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
|
rssiAverage = result.averageRssi(basic.rssi),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val DEVICE_CODE = 0x1420.toUShort()
|
||||||
|
private val TAG = logTag("PodDevice", "Apple", "Fake", "AirPods", "Pro2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 13 KiB |
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||||
|
<string name="app_name_pro">CAPod Pro</string>
|
||||||
<string name="general_value_not_available_label">Δε διατίθεται</string>
|
<string name="general_value_not_available_label">Δε διατίθεται</string>
|
||||||
<string name="general_error_label">Σφάλμα</string>
|
<string name="general_error_label">Σφάλμα</string>
|
||||||
<string name="general_grant_permission_action">Παραχώρηση άδειας</string>
|
<string name="general_grant_permission_action">Παραχώρηση άδειας</string>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||||
|
<string name="app_name">CAPod</string>
|
||||||
|
<string name="app_name_pro">CAPod Pro</string>
|
||||||
<string name="general_error_label">Erro</string>
|
<string name="general_error_label">Erro</string>
|
||||||
<string name="general_grant_permission_action">Conceder permissão</string>
|
<string name="general_grant_permission_action">Conceder permissão</string>
|
||||||
<string name="overview_nomaindevice_label">Nenhum dispositivo principal</string>
|
<string name="overview_nomaindevice_label">Nenhum dispositivo principal</string>
|
||||||
@@ -10,6 +12,7 @@
|
|||||||
<string name="permission_bluetooth_connect_description">Este aplicativo requer a permissão \"Conexão Bluetooth\" para interagir com dispositivos emparelhados e iniciar conexões.</string>
|
<string name="permission_bluetooth_connect_description">Este aplicativo requer a permissão \"Conexão Bluetooth\" para interagir com dispositivos emparelhados e iniciar conexões.</string>
|
||||||
<string name="permission_bluetooth_scan_label">Escaneando bluetooth</string>
|
<string name="permission_bluetooth_scan_label">Escaneando bluetooth</string>
|
||||||
<string name="permission_bluetooth_scan_description">A permissão \"Escaneamento de Bluetooth\" permite que este aplicativo descubra e receba dados de Bluetooth de dispositivos próximos, como seus AirPods.</string>
|
<string name="permission_bluetooth_scan_description">A permissão \"Escaneamento de Bluetooth\" permite que este aplicativo descubra e receba dados de Bluetooth de dispositivos próximos, como seus AirPods.</string>
|
||||||
|
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||||
<string name="permission_bluetooth_description">Este aplicativo requer a permissão \"Bluetooth\" para se conectar a dispositivos bluetooth emparelhados.</string>
|
<string name="permission_bluetooth_description">Este aplicativo requer a permissão \"Bluetooth\" para se conectar a dispositivos bluetooth emparelhados.</string>
|
||||||
<string name="permission_access_fine_location_label">Acesse a boa localização</string>
|
<string name="permission_access_fine_location_label">Acesse a boa localização</string>
|
||||||
<string name="permission_access_fine_location_description">O CAPod usa a permissão \"Boa localização\" para receber dados de Bluetooth Low Energy. Seus fones de ouvido usam a tecnologia Bluetooth Low Energy para transmitir seu status. Este aplicativo NÃO usará dados Bluetooth para determinar sua localização.</string>
|
<string name="permission_access_fine_location_description">O CAPod usa a permissão \"Boa localização\" para receber dados de Bluetooth Low Energy. Seus fones de ouvido usam a tecnologia Bluetooth Low Energy para transmitir seu status. Este aplicativo NÃO usará dados Bluetooth para determinar sua localização.</string>
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ class FakeAirPodsGen1Test : BaseAirPodsTest() {
|
|||||||
batteryLeftPodPercent shouldBe 1.0f
|
batteryLeftPodPercent shouldBe 1.0f
|
||||||
batteryRightPodPercent shouldBe 1.0f
|
batteryRightPodPercent shouldBe 1.0f
|
||||||
|
|
||||||
|
isLeftPodInEar shouldBe false
|
||||||
|
isRightPodInEar shouldBe false
|
||||||
|
|
||||||
|
isLeftPodCharging shouldBe true
|
||||||
|
isRightPodCharging shouldBe true
|
||||||
|
|
||||||
isCaseCharging shouldBe false
|
isCaseCharging shouldBe false
|
||||||
|
|
||||||
batteryCasePercent shouldBe 0.7f
|
batteryCasePercent shouldBe 0.7f
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ class FakeAirPodsGen3Test : BaseAirPodsTest() {
|
|||||||
batteryLeftPodPercent shouldBe 1f
|
batteryLeftPodPercent shouldBe 1f
|
||||||
batteryRightPodPercent shouldBe 1f
|
batteryRightPodPercent shouldBe 1f
|
||||||
|
|
||||||
|
isLeftPodInEar shouldBe false
|
||||||
|
isRightPodInEar shouldBe false
|
||||||
|
|
||||||
isCaseCharging shouldBe false
|
isCaseCharging shouldBe false
|
||||||
isLeftPodCharging shouldBe true
|
isLeftPodCharging shouldBe true
|
||||||
isRightPodCharging shouldBe true
|
isRightPodCharging shouldBe true
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package eu.darken.capod.pods.core.apple.misc
|
||||||
|
|
||||||
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
|
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class FakeAirPodsPro2Test : BaseAirPodsTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `guessed data`() = runTest {
|
||||||
|
create<FakeAirPodsPro2>("07 13 01 14 20 75 AA 58 35 00 10 00 E4 E4 26 00 00 00 00 00 00") {
|
||||||
|
rawPrefix shouldBe 0x01.toUByte()
|
||||||
|
rawDeviceModel shouldBe 0x1420.toUShort()
|
||||||
|
rawStatus shouldBe 0x75.toUByte()
|
||||||
|
rawPodsBattery shouldBe 0xAA.toUByte()
|
||||||
|
rawFlags shouldBe 0x5.toUShort()
|
||||||
|
rawCaseBattery shouldBe 0x8.toUShort()
|
||||||
|
rawCaseLidState shouldBe 0x35.toUByte()
|
||||||
|
rawDeviceColor shouldBe 0x00.toUByte()
|
||||||
|
rawSuffix shouldBe 0x10.toUByte()
|
||||||
|
|
||||||
|
batteryLeftPodPercent shouldBe 1.0f
|
||||||
|
batteryRightPodPercent shouldBe 1.0f
|
||||||
|
|
||||||
|
isCaseCharging shouldBe true
|
||||||
|
|
||||||
|
batteryCasePercent shouldBe 0.8f
|
||||||
|
|
||||||
|
model shouldBe PodDevice.Model.FAKE_AIRPODS_PRO2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,12 @@ class FakeAirPodsProTest : BaseAirPodsTest() {
|
|||||||
batteryLeftPodPercent shouldBe 1.0f
|
batteryLeftPodPercent shouldBe 1.0f
|
||||||
batteryRightPodPercent shouldBe 1.0f
|
batteryRightPodPercent shouldBe 1.0f
|
||||||
|
|
||||||
|
isLeftPodCharging shouldBe true
|
||||||
|
isRightPodCharging shouldBe true
|
||||||
|
|
||||||
|
isLeftPodInEar shouldBe false
|
||||||
|
isRightPodInEar shouldBe false
|
||||||
|
|
||||||
isCaseCharging shouldBe false
|
isCaseCharging shouldBe false
|
||||||
|
|
||||||
batteryCasePercent shouldBe 0.7f
|
batteryCasePercent shouldBe 0.7f
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:layout_marginBottom="2dp"
|
android:layout_marginBottom="2dp"
|
||||||
android:textStyle="bold"
|
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
tools:text="AirPods Max" />
|
tools:text="AirPods Max" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/pod_left_icon"
|
android:id="@+id/pod_left_icon"
|
||||||
style="@style/PodInfoItemIcon.Notification"
|
style="@style/PodInfoItemIcon.Notification"
|
||||||
android:src="@drawable/devic_airpods_gen1_left"
|
android:src="@drawable/devic_airpods_pro2_left"
|
||||||
tools:ignore="ContentDescription" />
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/pod_right_icon"
|
android:id="@+id/pod_right_icon"
|
||||||
style="@style/PodInfoItemIcon.Notification"
|
style="@style/PodInfoItemIcon.Notification"
|
||||||
android:src="@drawable/devic_airpods_gen1_right"
|
android:src="@drawable/devic_airpods_pro2_right"
|
||||||
tools:ignore="ContentDescription" />
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
### Updated by release.sh ###
|
### Updated by release.sh ###
|
||||||
project.versioning.major=2
|
project.versioning.major=2
|
||||||
project.versioning.minor=5
|
project.versioning.minor=5
|
||||||
project.versioning.patch=0
|
project.versioning.patch=2
|
||||||
project.versioning.build=1
|
project.versioning.build=0
|
||||||
#############################
|
#############################
|
||||||
|
|||||||