mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-15 02:36:12 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c437decbbf | ||
|
|
1d36677ead | ||
|
|
542b69ca92 | ||
|
|
8547c9c396 | ||
|
|
4e258e65c1 | ||
|
|
9f49b470fe | ||
|
|
4a71cfe65a | ||
|
|
d06959f964 | ||
|
|
8c339ca2eb |
@@ -1,3 +1,4 @@
|
|||||||
.idea
|
.idea
|
||||||
.gradle
|
.gradle
|
||||||
build/
|
build/
|
||||||
|
/fastlane/report.xml
|
||||||
|
|||||||
+218
@@ -0,0 +1,218 @@
|
|||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
CFPropertyList (3.0.5)
|
||||||
|
rexml
|
||||||
|
addressable (2.8.0)
|
||||||
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
|
artifactory (3.0.15)
|
||||||
|
atomos (0.1.3)
|
||||||
|
aws-eventstream (1.2.0)
|
||||||
|
aws-partitions (1.554.0)
|
||||||
|
aws-sdk-core (3.126.0)
|
||||||
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
|
aws-partitions (~> 1, >= 1.525.0)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
jmespath (~> 1.0)
|
||||||
|
aws-sdk-kms (1.54.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.126.0)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
aws-sdk-s3 (1.112.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.126.0)
|
||||||
|
aws-sdk-kms (~> 1)
|
||||||
|
aws-sigv4 (~> 1.4)
|
||||||
|
aws-sigv4 (1.4.0)
|
||||||
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
|
babosa (1.0.4)
|
||||||
|
claide (1.1.0)
|
||||||
|
colored (1.2)
|
||||||
|
colored2 (3.1.2)
|
||||||
|
commander (4.6.0)
|
||||||
|
highline (~> 2.0.0)
|
||||||
|
declarative (0.0.20)
|
||||||
|
digest-crc (0.6.4)
|
||||||
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
|
domain_name (0.5.20190701)
|
||||||
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
|
dotenv (2.7.6)
|
||||||
|
emoji_regex (3.2.3)
|
||||||
|
excon (0.91.0)
|
||||||
|
faraday (1.9.3)
|
||||||
|
faraday-em_http (~> 1.0)
|
||||||
|
faraday-em_synchrony (~> 1.0)
|
||||||
|
faraday-excon (~> 1.1)
|
||||||
|
faraday-httpclient (~> 1.0)
|
||||||
|
faraday-multipart (~> 1.0)
|
||||||
|
faraday-net_http (~> 1.0)
|
||||||
|
faraday-net_http_persistent (~> 1.0)
|
||||||
|
faraday-patron (~> 1.0)
|
||||||
|
faraday-rack (~> 1.0)
|
||||||
|
faraday-retry (~> 1.0)
|
||||||
|
ruby2_keywords (>= 0.0.4)
|
||||||
|
faraday-cookie_jar (0.0.7)
|
||||||
|
faraday (>= 0.8.0)
|
||||||
|
http-cookie (~> 1.0.0)
|
||||||
|
faraday-em_http (1.0.0)
|
||||||
|
faraday-em_synchrony (1.0.0)
|
||||||
|
faraday-excon (1.1.0)
|
||||||
|
faraday-httpclient (1.0.1)
|
||||||
|
faraday-multipart (1.0.3)
|
||||||
|
multipart-post (>= 1.2, < 3)
|
||||||
|
faraday-net_http (1.0.1)
|
||||||
|
faraday-net_http_persistent (1.2.0)
|
||||||
|
faraday-patron (1.0.0)
|
||||||
|
faraday-rack (1.0.0)
|
||||||
|
faraday-retry (1.0.3)
|
||||||
|
faraday_middleware (1.2.0)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
fastimage (2.2.6)
|
||||||
|
fastlane (2.204.3)
|
||||||
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
|
addressable (>= 2.8, < 3.0.0)
|
||||||
|
artifactory (~> 3.0)
|
||||||
|
aws-sdk-s3 (~> 1.0)
|
||||||
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
|
colored
|
||||||
|
commander (~> 4.6)
|
||||||
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
|
faraday_middleware (~> 1.0)
|
||||||
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
|
google-apis-androidpublisher_v3 (~> 0.3)
|
||||||
|
google-apis-playcustomapp_v1 (~> 0.1)
|
||||||
|
google-cloud-storage (~> 1.31)
|
||||||
|
highline (~> 2.0)
|
||||||
|
json (< 3.0.0)
|
||||||
|
jwt (>= 2.1.0, < 3)
|
||||||
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
|
multipart-post (~> 2.0.0)
|
||||||
|
naturally (~> 2.2)
|
||||||
|
optparse (~> 0.1.1)
|
||||||
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
|
security (= 0.1.3)
|
||||||
|
simctl (~> 1.6.3)
|
||||||
|
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||||
|
terminal-table (>= 1.4.5, < 2.0.0)
|
||||||
|
tty-screen (>= 0.6.3, < 1.0.0)
|
||||||
|
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||||
|
word_wrap (~> 1.0.0)
|
||||||
|
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||||
|
xcpretty (~> 0.3.0)
|
||||||
|
xcpretty-travis-formatter (>= 0.0.3)
|
||||||
|
gh_inspector (1.1.3)
|
||||||
|
google-apis-androidpublisher_v3 (0.16.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-apis-core (0.4.2)
|
||||||
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
|
mini_mime (~> 1.0)
|
||||||
|
representable (~> 3.0)
|
||||||
|
retriable (>= 2.0, < 4.a)
|
||||||
|
rexml
|
||||||
|
webrick
|
||||||
|
google-apis-iamcredentials_v1 (0.10.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-apis-playcustomapp_v1 (0.7.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-apis-storage_v1 (0.11.0)
|
||||||
|
google-apis-core (>= 0.4, < 2.a)
|
||||||
|
google-cloud-core (1.6.0)
|
||||||
|
google-cloud-env (~> 1.0)
|
||||||
|
google-cloud-errors (~> 1.0)
|
||||||
|
google-cloud-env (1.5.0)
|
||||||
|
faraday (>= 0.17.3, < 2.0)
|
||||||
|
google-cloud-errors (1.2.0)
|
||||||
|
google-cloud-storage (1.36.1)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
digest-crc (~> 0.4)
|
||||||
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
|
google-apis-storage_v1 (~> 0.1)
|
||||||
|
google-cloud-core (~> 1.6)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
|
mini_mime (~> 1.0)
|
||||||
|
googleauth (1.1.0)
|
||||||
|
faraday (>= 0.17.3, < 2.0)
|
||||||
|
jwt (>= 1.4, < 3.0)
|
||||||
|
memoist (~> 0.16)
|
||||||
|
multi_json (~> 1.11)
|
||||||
|
os (>= 0.9, < 2.0)
|
||||||
|
signet (>= 0.16, < 2.a)
|
||||||
|
highline (2.0.3)
|
||||||
|
http-cookie (1.0.4)
|
||||||
|
domain_name (~> 0.5)
|
||||||
|
httpclient (2.8.3)
|
||||||
|
jmespath (1.5.0)
|
||||||
|
json (2.6.1)
|
||||||
|
jwt (2.3.0)
|
||||||
|
memoist (0.16.2)
|
||||||
|
mini_magick (4.11.0)
|
||||||
|
mini_mime (1.1.2)
|
||||||
|
multi_json (1.15.0)
|
||||||
|
multipart-post (2.0.0)
|
||||||
|
nanaimo (0.3.0)
|
||||||
|
naturally (2.2.1)
|
||||||
|
optparse (0.1.1)
|
||||||
|
os (1.1.4)
|
||||||
|
plist (3.6.0)
|
||||||
|
public_suffix (4.0.6)
|
||||||
|
rake (13.0.6)
|
||||||
|
representable (3.1.1)
|
||||||
|
declarative (< 0.1.0)
|
||||||
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
|
uber (< 0.2.0)
|
||||||
|
retriable (3.1.2)
|
||||||
|
rexml (3.2.5)
|
||||||
|
rouge (2.0.7)
|
||||||
|
ruby2_keywords (0.0.5)
|
||||||
|
rubyzip (2.3.2)
|
||||||
|
security (0.1.3)
|
||||||
|
signet (0.16.0)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
faraday (>= 0.17.3, < 2.0)
|
||||||
|
jwt (>= 1.5, < 3.0)
|
||||||
|
multi_json (~> 1.10)
|
||||||
|
simctl (1.6.8)
|
||||||
|
CFPropertyList
|
||||||
|
naturally
|
||||||
|
terminal-notifier (2.0.0)
|
||||||
|
terminal-table (1.8.0)
|
||||||
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
|
trailblazer-option (0.1.2)
|
||||||
|
tty-cursor (0.7.1)
|
||||||
|
tty-screen (0.8.1)
|
||||||
|
tty-spinner (0.9.3)
|
||||||
|
tty-cursor (~> 0.7)
|
||||||
|
uber (0.1.0)
|
||||||
|
unf (0.1.4)
|
||||||
|
unf_ext
|
||||||
|
unf_ext (0.0.8)
|
||||||
|
unicode-display_width (1.8.0)
|
||||||
|
webrick (1.7.0)
|
||||||
|
word_wrap (1.0.0)
|
||||||
|
xcodeproj (1.21.0)
|
||||||
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
|
atomos (~> 0.1.3)
|
||||||
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
colored2 (~> 3.1)
|
||||||
|
nanaimo (~> 0.3.0)
|
||||||
|
rexml (~> 3.2.4)
|
||||||
|
xcpretty (0.3.0)
|
||||||
|
rouge (~> 2.0.7)
|
||||||
|
xcpretty-travis-formatter (1.0.1)
|
||||||
|
xcpretty (~> 0.2, >= 0.0.7)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
x86_64-linux
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
fastlane
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.2.8
|
||||||
@@ -14,9 +14,8 @@ import eu.darken.capod.common.lists.modular.mods.TypedVHCreatorMod
|
|||||||
import eu.darken.capod.main.ui.overview.cards.BluetoothDisabledVH
|
import eu.darken.capod.main.ui.overview.cards.BluetoothDisabledVH
|
||||||
import eu.darken.capod.main.ui.overview.cards.MissingMainDeviceVH
|
import eu.darken.capod.main.ui.overview.cards.MissingMainDeviceVH
|
||||||
import eu.darken.capod.main.ui.overview.cards.PermissionCardVH
|
import eu.darken.capod.main.ui.overview.cards.PermissionCardVH
|
||||||
import eu.darken.capod.main.ui.overview.cards.pods.BasicSingleApplePodsCardVH
|
import eu.darken.capod.main.ui.overview.cards.pods.DualPodsCardVH
|
||||||
import eu.darken.capod.main.ui.overview.cards.pods.DualApplePodsCardVH
|
import eu.darken.capod.main.ui.overview.cards.pods.SinglePodsCardVH
|
||||||
import eu.darken.capod.main.ui.overview.cards.pods.SingleApplePodsCardVH
|
|
||||||
import eu.darken.capod.main.ui.overview.cards.pods.UnknownPodDeviceCardVH
|
import eu.darken.capod.main.ui.overview.cards.pods.UnknownPodDeviceCardVH
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -29,9 +28,8 @@ class OverviewAdapter @Inject constructor() :
|
|||||||
init {
|
init {
|
||||||
modules.add(DataBinderMod(data))
|
modules.add(DataBinderMod(data))
|
||||||
modules.add(TypedVHCreatorMod({ data[it] is PermissionCardVH.Item }) { PermissionCardVH(it) })
|
modules.add(TypedVHCreatorMod({ data[it] is PermissionCardVH.Item }) { PermissionCardVH(it) })
|
||||||
modules.add(TypedVHCreatorMod({ data[it] is DualApplePodsCardVH.Item }) { DualApplePodsCardVH(it) })
|
modules.add(TypedVHCreatorMod({ data[it] is DualPodsCardVH.Item }) { DualPodsCardVH(it) })
|
||||||
modules.add(TypedVHCreatorMod({ data[it] is SingleApplePodsCardVH.Item }) { SingleApplePodsCardVH(it) })
|
modules.add(TypedVHCreatorMod({ data[it] is SinglePodsCardVH.Item }) { SinglePodsCardVH(it) })
|
||||||
modules.add(TypedVHCreatorMod({ data[it] is BasicSingleApplePodsCardVH.Item }) { BasicSingleApplePodsCardVH(it) })
|
|
||||||
modules.add(TypedVHCreatorMod({ data[it] is MissingMainDeviceVH.Item }) { MissingMainDeviceVH(it) })
|
modules.add(TypedVHCreatorMod({ data[it] is MissingMainDeviceVH.Item }) { MissingMainDeviceVH(it) })
|
||||||
modules.add(TypedVHCreatorMod({ data[it] is BluetoothDisabledVH.Item }) { BluetoothDisabledVH(it) })
|
modules.add(TypedVHCreatorMod({ data[it] is BluetoothDisabledVH.Item }) { BluetoothDisabledVH(it) })
|
||||||
modules.add(TypedVHCreatorMod({ data[it] is UnknownPodDeviceCardVH.Item }) { UnknownPodDeviceCardVH(it) })
|
modules.add(TypedVHCreatorMod({ data[it] is UnknownPodDeviceCardVH.Item }) { UnknownPodDeviceCardVH(it) })
|
||||||
|
|||||||
@@ -20,23 +20,23 @@ import eu.darken.capod.main.core.PermissionTool
|
|||||||
import eu.darken.capod.main.ui.overview.cards.BluetoothDisabledVH
|
import eu.darken.capod.main.ui.overview.cards.BluetoothDisabledVH
|
||||||
import eu.darken.capod.main.ui.overview.cards.MissingMainDeviceVH
|
import eu.darken.capod.main.ui.overview.cards.MissingMainDeviceVH
|
||||||
import eu.darken.capod.main.ui.overview.cards.PermissionCardVH
|
import eu.darken.capod.main.ui.overview.cards.PermissionCardVH
|
||||||
import eu.darken.capod.main.ui.overview.cards.pods.*
|
import eu.darken.capod.main.ui.overview.cards.pods.DualPodsCardVH
|
||||||
|
import eu.darken.capod.main.ui.overview.cards.pods.SinglePodsCardVH
|
||||||
|
import eu.darken.capod.main.ui.overview.cards.pods.UnknownPodDeviceCardVH
|
||||||
import eu.darken.capod.monitor.core.PodMonitor
|
import eu.darken.capod.monitor.core.PodMonitor
|
||||||
import eu.darken.capod.monitor.core.worker.MonitorControl
|
import eu.darken.capod.monitor.core.worker.MonitorControl
|
||||||
|
import eu.darken.capod.pods.core.DualPodDevice
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
import eu.darken.capod.pods.core.SinglePodDevice
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
|
||||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class OverviewFragmentVM @Inject constructor(
|
class OverviewFragmentVM @Inject constructor(
|
||||||
handle: SavedStateHandle,
|
@Suppress("UNUSED_PARAMETER") handle: SavedStateHandle,
|
||||||
dispatcherProvider: DispatcherProvider,
|
dispatcherProvider: DispatcherProvider,
|
||||||
private val monitorControl: MonitorControl,
|
private val monitorControl: MonitorControl,
|
||||||
private val podMonitor: PodMonitor,
|
private val podMonitor: PodMonitor,
|
||||||
@@ -74,7 +74,7 @@ class OverviewFragmentVM @Inject constructor(
|
|||||||
monitorControl.startMonitor()
|
monitorControl.startMonitor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map { Unit }
|
.map { }
|
||||||
.asLiveData2()
|
.asLiveData2()
|
||||||
|
|
||||||
val requestPermissionEvent = SingleLiveEvent<Permission>()
|
val requestPermissionEvent = SingleLiveEvent<Permission>()
|
||||||
@@ -129,19 +129,13 @@ class OverviewFragmentVM @Inject constructor(
|
|||||||
pods.map {
|
pods.map {
|
||||||
val now = Instant.now()
|
val now = Instant.now()
|
||||||
when (it) {
|
when (it) {
|
||||||
is DualApplePods -> DualApplePodsCardVH.Item(
|
is DualPodDevice -> DualPodsCardVH.Item(
|
||||||
now = now,
|
now = now,
|
||||||
device = it,
|
device = it,
|
||||||
showDebug = isDebugMode,
|
showDebug = isDebugMode,
|
||||||
isMainPod = it == mainPod,
|
isMainPod = it == mainPod,
|
||||||
)
|
)
|
||||||
is SingleApplePods -> SingleApplePodsCardVH.Item(
|
is SinglePodDevice -> SinglePodsCardVH.Item(
|
||||||
now = now,
|
|
||||||
device = it,
|
|
||||||
showDebug = isDebugMode,
|
|
||||||
isMainPod = it == mainPod,
|
|
||||||
)
|
|
||||||
is BasicSingleApplePods -> BasicSingleApplePodsCardVH.Item(
|
|
||||||
now = now,
|
now = now,
|
||||||
device = it,
|
device = it,
|
||||||
showDebug = isDebugMode,
|
showDebug = isDebugMode,
|
||||||
|
|||||||
-58
@@ -1,58 +0,0 @@
|
|||||||
package eu.darken.capod.main.ui.overview.cards.pods
|
|
||||||
|
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.view.isGone
|
|
||||||
import eu.darken.capod.R
|
|
||||||
import eu.darken.capod.common.lists.binding
|
|
||||||
import eu.darken.capod.databinding.OverviewPodsAppleSingleBasicItemBinding
|
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
|
||||||
import eu.darken.capod.pods.core.getBatteryDrawable
|
|
||||||
import eu.darken.capod.pods.core.getBatteryLevelHeadset
|
|
||||||
import eu.darken.capod.pods.core.lastSeenFormatted
|
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
class BasicSingleApplePodsCardVH(parent: ViewGroup) :
|
|
||||||
PodDeviceVH<BasicSingleApplePodsCardVH.Item, OverviewPodsAppleSingleBasicItemBinding>(
|
|
||||||
R.layout.overview_pods_apple_single_basic_item,
|
|
||||||
parent
|
|
||||||
) {
|
|
||||||
|
|
||||||
override val viewBinding = lazy { OverviewPodsAppleSingleBasicItemBinding.bind(itemView) }
|
|
||||||
|
|
||||||
override val onBindData = binding(payload = true) { item: Item ->
|
|
||||||
val device = item.device
|
|
||||||
|
|
||||||
name.apply {
|
|
||||||
text = device.getLabel(context)
|
|
||||||
if (item.isMainPod) setTypeface(typeface, Typeface.BOLD)
|
|
||||||
else setTypeface(typeface, Typeface.NORMAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceIcon.setImageResource(device.iconRes)
|
|
||||||
|
|
||||||
lastSeen.text = device.lastSeenFormatted(item.now)
|
|
||||||
|
|
||||||
reception.text = item.getReceptionText()
|
|
||||||
|
|
||||||
batteryLabel.text = device.getBatteryLevelHeadset(context)
|
|
||||||
batteryIcon.setImageResource(getBatteryDrawable(device.batteryHeadsetPercent))
|
|
||||||
|
|
||||||
status.apply {
|
|
||||||
val sb = StringBuilder()
|
|
||||||
if (item.showDebug) {
|
|
||||||
sb.append("--- Debug ---")
|
|
||||||
sb.append("\n").append(device.rawDataHex)
|
|
||||||
}
|
|
||||||
text = sb
|
|
||||||
isGone = !item.showDebug
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Item(
|
|
||||||
override val now: Instant,
|
|
||||||
override val device: BasicSingleApplePods,
|
|
||||||
override val showDebug: Boolean,
|
|
||||||
override val isMainPod: Boolean,
|
|
||||||
) : PodDeviceVH.Item
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package eu.darken.capod.main.ui.overview.cards.pods
|
|
||||||
|
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.view.isGone
|
|
||||||
import androidx.core.view.isInvisible
|
|
||||||
import eu.darken.capod.R
|
|
||||||
import eu.darken.capod.common.lists.binding
|
|
||||||
import eu.darken.capod.databinding.OverviewPodsAppleDualItemBinding
|
|
||||||
import eu.darken.capod.pods.core.*
|
|
||||||
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 java.time.Duration
|
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
class DualApplePodsCardVH(parent: ViewGroup) :
|
|
||||||
PodDeviceVH<DualApplePodsCardVH.Item, OverviewPodsAppleDualItemBinding>(
|
|
||||||
R.layout.overview_pods_apple_dual_item,
|
|
||||||
parent
|
|
||||||
) {
|
|
||||||
|
|
||||||
override val viewBinding = lazy { OverviewPodsAppleDualItemBinding.bind(itemView) }
|
|
||||||
|
|
||||||
override val onBindData = binding(payload = true) { item: Item ->
|
|
||||||
val device = item.device
|
|
||||||
name.apply {
|
|
||||||
val sb = StringBuilder(device.getLabel(context))
|
|
||||||
if (!listOf(DeviceColor.WHITE, DeviceColor.UNKNOWN).contains(device.deviceColor)) {
|
|
||||||
sb.append(" (${device.deviceColor.name})")
|
|
||||||
}
|
|
||||||
text = sb
|
|
||||||
if (item.isMainPod) setTypeface(typeface, Typeface.BOLD)
|
|
||||||
else setTypeface(typeface, Typeface.NORMAL)
|
|
||||||
if (item.showDebug) {
|
|
||||||
append(" [${device.primaryPod.name}]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deviceIcon.setImageResource(device.iconRes)
|
|
||||||
|
|
||||||
lastSeen.text = context.getString(R.string.last_seen_x, device.lastSeenFormatted(item.now))
|
|
||||||
firstSeen.text = context.getString(R.string.first_seen_x, device.firstSeenFormatted(item.now))
|
|
||||||
firstSeen.isGone = Duration.between(device.seenFirstAt, device.seenLastAt).toMinutes() < 1
|
|
||||||
|
|
||||||
reception.text = item.getReceptionText()
|
|
||||||
|
|
||||||
// Left Pod
|
|
||||||
device.apply {
|
|
||||||
podLeftBatteryIcon.setImageResource(getBatteryDrawable(batteryLeftPodPercent))
|
|
||||||
podLeftBatteryLabel.text = getBatteryLevelLeftPod(context)
|
|
||||||
|
|
||||||
podLeftChargingIcon.isInvisible = !isLeftPodCharging
|
|
||||||
podLeftChargingLabel.isInvisible = !isLeftPodCharging
|
|
||||||
|
|
||||||
podLeftMicrophoneIcon.isInvisible = !isLeftPodMicrophone
|
|
||||||
podLeftMicrophoneLabel.isInvisible = !isLeftPodMicrophone
|
|
||||||
|
|
||||||
podLeftWearIcon.isInvisible = !isLeftPodInEar
|
|
||||||
podLeftWearLabel.isInvisible = !isLeftPodInEar
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right Pod
|
|
||||||
device.apply {
|
|
||||||
podRightBatteryIcon.setImageResource(getBatteryDrawable(batteryRightPodPercent))
|
|
||||||
podRightBatteryLabel.text = getBatteryLevelRightPod(context)
|
|
||||||
|
|
||||||
podRightChargingIcon.isInvisible = !isRightPodCharging
|
|
||||||
podRightChargingLabel.isInvisible = !isRightPodCharging
|
|
||||||
|
|
||||||
podRightMicrophoneIcon.isInvisible = !isRightPodMicrophone
|
|
||||||
podRightMicrophoneLabel.isInvisible = !isRightPodMicrophone
|
|
||||||
|
|
||||||
podRightWearIcon.isInvisible = !isRightPodInEar
|
|
||||||
podRightWearLabel.isInvisible = !isRightPodInEar
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case
|
|
||||||
device.apply {
|
|
||||||
podCaseBatteryIcon.setImageResource(getBatteryDrawable(batteryCasePercent))
|
|
||||||
podCaseBatteryLabel.text = getBatteryLevelCase(context)
|
|
||||||
|
|
||||||
podCaseChargingIcon.isInvisible = !isCaseCharging
|
|
||||||
podCaseChargingLabel.isInvisible = !isCaseCharging
|
|
||||||
|
|
||||||
podCaseLidLabel.text = when (caseLidState) {
|
|
||||||
LidState.OPEN -> context.getString(R.string.pods_case_status_open_label)
|
|
||||||
LidState.CLOSED -> context.getString(R.string.pods_case_status_closed_label)
|
|
||||||
else -> context.getString(R.string.pods_case_unknown_state)
|
|
||||||
}
|
|
||||||
|
|
||||||
val hideInfo = !listOf(LidState.OPEN, LidState.CLOSED).contains(caseLidState)
|
|
||||||
podCaseLidIcon.isInvisible = hideInfo
|
|
||||||
podCaseLidLabel.isInvisible = hideInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
status.apply {
|
|
||||||
val sb = StringBuilder(device.getConnectionStateLabel(context))
|
|
||||||
if (item.showDebug) {
|
|
||||||
sb.append("\n\n").append("---Debug---")
|
|
||||||
sb.append("\n").append(device.rawDataHex)
|
|
||||||
}
|
|
||||||
text = sb
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Item(
|
|
||||||
override val now: Instant,
|
|
||||||
override val device: DualApplePods,
|
|
||||||
override val showDebug: Boolean,
|
|
||||||
override val isMainPod: Boolean,
|
|
||||||
) : PodDeviceVH.Item
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package eu.darken.capod.main.ui.overview.cards.pods
|
||||||
|
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.lists.binding
|
||||||
|
import eu.darken.capod.databinding.OverviewPodsDualItemBinding
|
||||||
|
import eu.darken.capod.pods.core.*
|
||||||
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
|
import eu.darken.capod.pods.core.apple.DualAirPods.LidState
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
class DualPodsCardVH(parent: ViewGroup) :
|
||||||
|
PodDeviceVH<DualPodsCardVH.Item, OverviewPodsDualItemBinding>(
|
||||||
|
R.layout.overview_pods_dual_item,
|
||||||
|
parent
|
||||||
|
) {
|
||||||
|
|
||||||
|
override val viewBinding = lazy { OverviewPodsDualItemBinding.bind(itemView) }
|
||||||
|
|
||||||
|
override val onBindData = binding(payload = true) { item: Item ->
|
||||||
|
val device = item.device
|
||||||
|
name.apply {
|
||||||
|
val sb = StringBuilder(device.getLabel(context))
|
||||||
|
if (device is HasPodStyle && item.showDebug) {
|
||||||
|
val style = device.podStyle
|
||||||
|
sb.append(" (${style.getColor(context)})")
|
||||||
|
}
|
||||||
|
text = sb
|
||||||
|
|
||||||
|
if (item.isMainPod) setTypeface(typeface, Typeface.BOLD)
|
||||||
|
else setTypeface(typeface, Typeface.NORMAL)
|
||||||
|
|
||||||
|
if (device is DualAirPods && item.showDebug) {
|
||||||
|
append(" [${device.primaryPod.name}]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deviceIcon.setImageResource(device.iconRes)
|
||||||
|
|
||||||
|
lastSeen.text = context.getString(R.string.last_seen_x, device.lastSeenFormatted(item.now))
|
||||||
|
firstSeen.text = context.getString(R.string.first_seen_x, device.firstSeenFormatted(item.now))
|
||||||
|
firstSeen.isGone = Duration.between(device.seenFirstAt, device.seenLastAt).toMinutes() < 1
|
||||||
|
|
||||||
|
reception.text = item.getReceptionText()
|
||||||
|
|
||||||
|
// Pods battery state
|
||||||
|
device.apply {
|
||||||
|
podLeftBatteryIcon.setImageResource(getBatteryDrawable(batteryLeftPodPercent))
|
||||||
|
podLeftBatteryLabel.text = getBatteryLevelLeftPod(context)
|
||||||
|
|
||||||
|
podRightBatteryIcon.setImageResource(getBatteryDrawable(batteryRightPodPercent))
|
||||||
|
podRightBatteryLabel.text = getBatteryLevelRightPod(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pods charging state
|
||||||
|
device.apply {
|
||||||
|
if (this is HasChargeDetectionDual) {
|
||||||
|
podLeftChargingIcon.isInvisible = !isLeftPodCharging
|
||||||
|
podLeftChargingLabel.isInvisible = !isLeftPodCharging
|
||||||
|
|
||||||
|
podRightChargingIcon.isInvisible = !isRightPodCharging
|
||||||
|
podRightChargingLabel.isInvisible = !isRightPodCharging
|
||||||
|
} else {
|
||||||
|
podLeftChargingIcon.isGone = true
|
||||||
|
podLeftChargingLabel.isGone = true
|
||||||
|
|
||||||
|
podRightChargingIcon.isGone = true
|
||||||
|
podRightChargingLabel.isGone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Microphone state
|
||||||
|
device.apply {
|
||||||
|
if (this is HasDualMicrophone) {
|
||||||
|
podLeftMicrophoneIcon.isInvisible = !isLeftPodMicrophone
|
||||||
|
podLeftMicrophoneLabel.isInvisible = !isLeftPodMicrophone
|
||||||
|
|
||||||
|
podRightMicrophoneIcon.isInvisible = !isRightPodMicrophone
|
||||||
|
podRightMicrophoneLabel.isInvisible = !isRightPodMicrophone
|
||||||
|
} else {
|
||||||
|
podLeftMicrophoneIcon.isGone = true
|
||||||
|
podLeftMicrophoneLabel.isGone = true
|
||||||
|
|
||||||
|
podRightMicrophoneIcon.isGone = true
|
||||||
|
podRightMicrophoneLabel.isGone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pods wear state
|
||||||
|
device.apply {
|
||||||
|
if (this is HasEarDetectionDual) {
|
||||||
|
podLeftWearIcon.isInvisible = !isLeftPodInEar
|
||||||
|
podLeftWearLabel.isInvisible = !isLeftPodInEar
|
||||||
|
|
||||||
|
podRightWearIcon.isInvisible = !isRightPodInEar
|
||||||
|
podRightWearLabel.isInvisible = !isRightPodInEar
|
||||||
|
} else {
|
||||||
|
podLeftWearIcon.isGone = true
|
||||||
|
podLeftWearLabel.isGone = true
|
||||||
|
|
||||||
|
podRightWearIcon.isGone = true
|
||||||
|
podRightWearLabel.isGone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case charge state
|
||||||
|
device.apply {
|
||||||
|
if (this is HasCase) {
|
||||||
|
podCaseBatteryIcon.isGone = false
|
||||||
|
podCaseBatteryIcon.setImageResource(getBatteryDrawable(batteryCasePercent))
|
||||||
|
podCaseBatteryLabel.text = getBatteryLevelCase(context)
|
||||||
|
|
||||||
|
podCaseChargingIcon.isInvisible = !isCaseCharging
|
||||||
|
podCaseChargingLabel.isInvisible = !isCaseCharging
|
||||||
|
} else {
|
||||||
|
podCaseBatteryIcon.isGone = true
|
||||||
|
podCaseBatteryLabel.isGone = true
|
||||||
|
|
||||||
|
podCaseChargingIcon.isGone = true
|
||||||
|
podCaseChargingLabel.isGone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case lid state
|
||||||
|
device.apply {
|
||||||
|
if (this is DualAirPods) {
|
||||||
|
podCaseLidLabel.text = when (caseLidState) {
|
||||||
|
LidState.OPEN -> context.getString(R.string.pods_case_status_open_label)
|
||||||
|
LidState.CLOSED -> context.getString(R.string.pods_case_status_closed_label)
|
||||||
|
else -> context.getString(R.string.pods_case_unknown_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
val hideInfo = !listOf(LidState.OPEN, LidState.CLOSED).contains(caseLidState)
|
||||||
|
podCaseLidIcon.isInvisible = hideInfo
|
||||||
|
podCaseLidLabel.isInvisible = hideInfo
|
||||||
|
} else {
|
||||||
|
podCaseLidIcon.isGone = true
|
||||||
|
podCaseLidLabel.isGone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection state
|
||||||
|
device.apply {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
if (this is HasStateDetection) {
|
||||||
|
sb.append(state.getLabel(context))
|
||||||
|
}
|
||||||
|
if (item.showDebug) {
|
||||||
|
sb.append("\n\n").append("---Debug---")
|
||||||
|
sb.append("\n").append(rawDataHex)
|
||||||
|
}
|
||||||
|
status.text = sb
|
||||||
|
status.isGone = sb.isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Item(
|
||||||
|
override val now: Instant,
|
||||||
|
override val device: DualPodDevice,
|
||||||
|
override val showDebug: Boolean,
|
||||||
|
override val isMainPod: Boolean,
|
||||||
|
) : PodDeviceVH.Item
|
||||||
|
}
|
||||||
-65
@@ -1,65 +0,0 @@
|
|||||||
package eu.darken.capod.main.ui.overview.cards.pods
|
|
||||||
|
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.view.isGone
|
|
||||||
import androidx.core.view.isInvisible
|
|
||||||
import eu.darken.capod.R
|
|
||||||
import eu.darken.capod.common.lists.binding
|
|
||||||
import eu.darken.capod.databinding.OverviewPodsAppleSingleItemBinding
|
|
||||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
|
||||||
import eu.darken.capod.pods.core.getBatteryDrawable
|
|
||||||
import eu.darken.capod.pods.core.getBatteryLevelHeadset
|
|
||||||
import eu.darken.capod.pods.core.lastSeenFormatted
|
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
class SingleApplePodsCardVH(parent: ViewGroup) :
|
|
||||||
PodDeviceVH<SingleApplePodsCardVH.Item, OverviewPodsAppleSingleItemBinding>(
|
|
||||||
R.layout.overview_pods_apple_single_item,
|
|
||||||
parent
|
|
||||||
) {
|
|
||||||
|
|
||||||
override val viewBinding = lazy { OverviewPodsAppleSingleItemBinding.bind(itemView) }
|
|
||||||
|
|
||||||
override val onBindData = binding(payload = true) { item: Item ->
|
|
||||||
val device = item.device
|
|
||||||
|
|
||||||
name.apply {
|
|
||||||
text = device.getLabel(context)
|
|
||||||
if (item.isMainPod) setTypeface(typeface, Typeface.BOLD)
|
|
||||||
else setTypeface(typeface, Typeface.NORMAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceIcon.setImageResource(device.iconRes)
|
|
||||||
|
|
||||||
lastSeen.text = device.lastSeenFormatted(item.now)
|
|
||||||
|
|
||||||
reception.text = item.getReceptionText()
|
|
||||||
|
|
||||||
batteryLabel.text = device.getBatteryLevelHeadset(context)
|
|
||||||
batteryIcon.setImageResource(getBatteryDrawable(device.batteryHeadsetPercent))
|
|
||||||
|
|
||||||
chargingIcon.isInvisible = device.isHeadsetBeingCharged
|
|
||||||
chargingLabel.isInvisible = device.isHeadsetBeingCharged
|
|
||||||
|
|
||||||
wearIcon.isInvisible = device.isHeadphonesBeingWorn
|
|
||||||
wearLabel.isInvisible = device.isHeadphonesBeingWorn
|
|
||||||
|
|
||||||
status.apply {
|
|
||||||
val sb = StringBuilder()
|
|
||||||
if (item.showDebug) {
|
|
||||||
sb.append("--- Debug ---")
|
|
||||||
sb.append("\n").append(device.rawDataHex)
|
|
||||||
}
|
|
||||||
text = sb
|
|
||||||
isGone = !item.showDebug
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Item(
|
|
||||||
override val now: Instant,
|
|
||||||
override val device: SingleApplePods,
|
|
||||||
override val showDebug: Boolean,
|
|
||||||
override val isMainPod: Boolean,
|
|
||||||
) : PodDeviceVH.Item
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package eu.darken.capod.main.ui.overview.cards.pods
|
||||||
|
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.lists.binding
|
||||||
|
import eu.darken.capod.databinding.OverviewPodsSingleItemBinding
|
||||||
|
import eu.darken.capod.pods.core.*
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
class SinglePodsCardVH(parent: ViewGroup) :
|
||||||
|
PodDeviceVH<SinglePodsCardVH.Item, OverviewPodsSingleItemBinding>(
|
||||||
|
R.layout.overview_pods_single_item,
|
||||||
|
parent
|
||||||
|
) {
|
||||||
|
|
||||||
|
override val viewBinding = lazy { OverviewPodsSingleItemBinding.bind(itemView) }
|
||||||
|
|
||||||
|
override val onBindData = binding(payload = true) { item: Item ->
|
||||||
|
val device = item.device
|
||||||
|
|
||||||
|
name.apply {
|
||||||
|
text = device.getLabel(context)
|
||||||
|
if (item.isMainPod) setTypeface(typeface, Typeface.BOLD)
|
||||||
|
else setTypeface(typeface, Typeface.NORMAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceIcon.setImageResource(device.iconRes)
|
||||||
|
|
||||||
|
lastSeen.text = device.lastSeenFormatted(item.now)
|
||||||
|
|
||||||
|
reception.text = item.getReceptionText()
|
||||||
|
|
||||||
|
// Battery level
|
||||||
|
device.apply {
|
||||||
|
batteryLabel.text = getBatteryLevelHeadset(context)
|
||||||
|
batteryIcon.setImageResource(getBatteryDrawable(batteryHeadsetPercent))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Charge state
|
||||||
|
device.apply {
|
||||||
|
if (this is HasChargeDetection) {
|
||||||
|
chargingIcon.isInvisible = isHeadsetBeingCharged
|
||||||
|
chargingLabel.isInvisible = isHeadsetBeingCharged
|
||||||
|
} else {
|
||||||
|
chargingIcon.isGone = true
|
||||||
|
chargingLabel.isGone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has ear detection
|
||||||
|
device.apply {
|
||||||
|
if (this is HasEarDetection) {
|
||||||
|
wearIcon.isInvisible = isBeingWorn
|
||||||
|
wearLabel.isInvisible = isBeingWorn
|
||||||
|
} else {
|
||||||
|
wearIcon.isGone = true
|
||||||
|
wearLabel.isGone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status.apply {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
if (item.showDebug) {
|
||||||
|
sb.append("--- Debug ---")
|
||||||
|
sb.append("\n").append(device.rawDataHex)
|
||||||
|
}
|
||||||
|
text = sb
|
||||||
|
isGone = !item.showDebug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Item(
|
||||||
|
override val now: Instant,
|
||||||
|
override val device: SinglePodDevice,
|
||||||
|
override val showDebug: Boolean,
|
||||||
|
override val isMainPod: Boolean,
|
||||||
|
) : PodDeviceVH.Item
|
||||||
|
}
|
||||||
@@ -6,8 +6,7 @@ import android.widget.RemoteViews
|
|||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
import eu.darken.capod.pods.core.*
|
import eu.darken.capod.pods.core.*
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
|
||||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -17,13 +16,12 @@ class MonitorNotificationViewFactory @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
fun createContentView(device: PodDevice): RemoteViews = when (device) {
|
fun createContentView(device: PodDevice): RemoteViews = when (device) {
|
||||||
is DualApplePods -> createDualApplePods(device)
|
is DualAirPods -> createDualApplePods(device)
|
||||||
is SingleApplePods -> createSingleApplePods(device)
|
is SingleApplePods -> createSingleApplePods(device)
|
||||||
is BasicSingleApplePods -> createSingleBasicApplePods(device)
|
|
||||||
else -> createUnknownDevice(device)
|
else -> createUnknownDevice(device)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDualApplePods(device: DualApplePods): RemoteViews = RemoteViews(
|
private fun createDualApplePods(device: DualAirPods): RemoteViews = RemoteViews(
|
||||||
context.packageName,
|
context.packageName,
|
||||||
R.layout.monitor_notification_dual_pods_small
|
R.layout.monitor_notification_dual_pods_small
|
||||||
).apply {
|
).apply {
|
||||||
@@ -52,19 +50,12 @@ class MonitorNotificationViewFactory @Inject constructor(
|
|||||||
setTextViewText(R.id.headphones_label, getLabel(context))
|
setTextViewText(R.id.headphones_label, getLabel(context))
|
||||||
setImageViewResource(R.id.headphones_battery_icon, getBatteryDrawable(batteryHeadsetPercent))
|
setImageViewResource(R.id.headphones_battery_icon, getBatteryDrawable(batteryHeadsetPercent))
|
||||||
setTextViewText(R.id.headphones_battery_label, getBatteryLevelHeadset(context))
|
setTextViewText(R.id.headphones_battery_label, getBatteryLevelHeadset(context))
|
||||||
setViewVisibility(R.id.headphones_charging, if (isHeadsetBeingCharged) View.VISIBLE else View.GONE)
|
if (this is HasEarDetection) {
|
||||||
setViewVisibility(R.id.headphones_worn, if (isHeadphonesBeingWorn) View.VISIBLE else View.GONE)
|
setViewVisibility(R.id.headphones_worn, if (isBeingWorn) View.VISIBLE else View.GONE)
|
||||||
}
|
}
|
||||||
}
|
if (this is HasChargeDetectionDual) {
|
||||||
|
setViewVisibility(R.id.headphones_charging, if (isHeadsetBeingCharged) View.VISIBLE else View.GONE)
|
||||||
private fun createSingleBasicApplePods(device: BasicSingleApplePods): RemoteViews = RemoteViews(
|
}
|
||||||
context.packageName,
|
|
||||||
R.layout.monitor_notification_single_pods_basic_small
|
|
||||||
).apply {
|
|
||||||
device.apply {
|
|
||||||
setTextViewText(R.id.headphones_label, getLabel(context))
|
|
||||||
setImageViewResource(R.id.headphones_battery_icon, getBatteryDrawable(batteryHeadsetPercent))
|
|
||||||
setTextViewText(R.id.headphones_battery_label, getBatteryLevelHeadset(context))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package eu.darken.capod.pods.core
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
interface HasDualPods {
|
interface DualPodDevice : PodDevice {
|
||||||
|
|
||||||
enum class Pod {
|
enum class Pod {
|
||||||
LEFT,
|
LEFT,
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
|
interface HasChargeDetection {
|
||||||
|
|
||||||
|
val isHeadsetBeingCharged: Boolean
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
|
interface HasChargeDetectionDual : HasChargeDetection {
|
||||||
|
|
||||||
|
val isLeftPodCharging: Boolean
|
||||||
|
|
||||||
|
val isRightPodCharging: Boolean
|
||||||
|
|
||||||
|
val isEitherPodCharging: Boolean
|
||||||
|
get() = isLeftPodCharging || isRightPodCharging
|
||||||
|
|
||||||
|
override val isHeadsetBeingCharged: Boolean
|
||||||
|
get() = isEitherPodCharging
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
|
interface HasDualMicrophone {
|
||||||
|
|
||||||
|
val isLeftPodMicrophone: Boolean
|
||||||
|
|
||||||
|
val isRightPodMicrophone: Boolean
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package eu.darken.capod.pods.core
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
interface HasEarDetection : PodDevice {
|
interface HasEarDetection {
|
||||||
|
|
||||||
val isBeingWorn: Boolean
|
val isBeingWorn: Boolean
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package eu.darken.capod.pods.core
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
interface HasDualEarDetection : HasEarDetection {
|
interface HasEarDetectionDual : HasEarDetection {
|
||||||
|
|
||||||
val isLeftPodInEar: Boolean
|
val isLeftPodInEar: Boolean
|
||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.ColorRes
|
||||||
|
|
||||||
|
interface HasPodStyle {
|
||||||
|
|
||||||
|
val podStyle: PodStyle
|
||||||
|
|
||||||
|
interface PodStyle {
|
||||||
|
fun getLabel(context: Context): String
|
||||||
|
|
||||||
|
@ColorRes
|
||||||
|
fun getColor(context: Context): Int
|
||||||
|
|
||||||
|
val identifier: String
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
interface HasStateDetection {
|
||||||
|
|
||||||
|
val state: State
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
fun getLabel(context: Context): String
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -103,6 +103,9 @@ interface PodDevice {
|
|||||||
@Json(name = "fakes.tws.i99999") TWS_I99999(
|
@Json(name = "fakes.tws.i99999") TWS_I99999(
|
||||||
"TWS i99999"
|
"TWS i99999"
|
||||||
),
|
),
|
||||||
|
@Json(name = "fakes.varunr.airpodspro") VARUNR_AIRPODS_PRO(
|
||||||
|
"Varunr AirPods Pro"
|
||||||
|
),
|
||||||
@Json(name = "unknown") UNKNOWN(
|
@Json(name = "unknown") UNKNOWN(
|
||||||
"Unknown"
|
"Unknown"
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import java.time.Duration
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
fun HasDualPods.getBatteryLevelLeftPod(context: Context): String =
|
fun DualPodDevice.getBatteryLevelLeftPod(context: Context): String =
|
||||||
batteryLeftPodPercent?.let { "${(it * 100).roundToInt()}%" }
|
batteryLeftPodPercent?.let { "${(it * 100).roundToInt()}%" }
|
||||||
?: context.getString(R.string.general_value_not_available_label)
|
?: context.getString(R.string.general_value_not_available_label)
|
||||||
|
|
||||||
fun HasDualPods.getBatteryLevelRightPod(context: Context): String =
|
fun DualPodDevice.getBatteryLevelRightPod(context: Context): String =
|
||||||
batteryRightPodPercent?.let { "${(it * 100).roundToInt()}%" }
|
batteryRightPodPercent?.let { "${(it * 100).roundToInt()}%" }
|
||||||
?: context.getString(R.string.general_value_not_available_label)
|
?: context.getString(R.string.general_value_not_available_label)
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ fun HasCase.getBatteryLevelCase(context: Context): String =
|
|||||||
batteryCasePercent?.let { "${(it * 100).roundToInt()}%" }
|
batteryCasePercent?.let { "${(it * 100).roundToInt()}%" }
|
||||||
?: context.getString(R.string.general_value_not_available_label)
|
?: context.getString(R.string.general_value_not_available_label)
|
||||||
|
|
||||||
fun HasSinglePod.getBatteryLevelHeadset(context: Context): String =
|
fun SinglePodDevice.getBatteryLevelHeadset(context: Context): String =
|
||||||
batteryHeadsetPercent?.let { "${(it * 100).roundToInt()}%" }
|
batteryHeadsetPercent?.let { "${(it * 100).roundToInt()}%" }
|
||||||
?: context.getString(R.string.general_value_not_available_label)
|
?: context.getString(R.string.general_value_not_available_label)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package eu.darken.capod.pods.core
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
interface HasSinglePod {
|
interface SinglePodDevice : PodDevice {
|
||||||
|
|
||||||
val batteryHeadsetPercent: Float?
|
val batteryHeadsetPercent: Float?
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import eu.darken.capod.common.debug.logging.asLog
|
|||||||
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.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
|
import eu.darken.capod.pods.core.apple.misc.UnknownAppleDevice
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
|
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import dagger.hilt.components.SingletonComponent
|
|||||||
import dagger.multibindings.IntoSet
|
import dagger.multibindings.IntoSet
|
||||||
import eu.darken.capod.pods.core.apple.airpods.*
|
import eu.darken.capod.pods.core.apple.airpods.*
|
||||||
import eu.darken.capod.pods.core.apple.beats.*
|
import eu.darken.capod.pods.core.apple.beats.*
|
||||||
import eu.darken.capod.pods.core.apple.fakes.Twsi99999
|
import eu.darken.capod.pods.core.apple.misc.Twsi99999
|
||||||
|
import eu.darken.capod.pods.core.apple.misc.VarunrAirPodsPro
|
||||||
|
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
@Module
|
@Module
|
||||||
@@ -28,4 +29,6 @@ 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 fakesTwsi999999(factory: Twsi99999.Factory): ApplePodsFactory<out ApplePods>
|
@Binds @IntoSet abstract fun fakesTwsi999999(factory: Twsi99999.Factory): ApplePodsFactory<out ApplePods>
|
||||||
|
@Binds @IntoSet
|
||||||
|
abstract fun fakesVarunrAirPodsPro(factory: VarunrAirPodsPro.Factory): ApplePodsFactory<out ApplePods>
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,9 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
|||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
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.HasCase
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
|
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -21,7 +23,7 @@ abstract class ApplePodsFactory<PodType : ApplePods>(private val tag: String) {
|
|||||||
val deviceColor: UByte,
|
val deviceColor: UByte,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun ProximityPairing.Message.getApplePodsMarkings(): Markings = Markings(
|
private fun ProximityPairing.Message.getApplePodsMarkings(): Markings = Markings(
|
||||||
vendor = ProximityPairing.CONTINUITY_PROTOCOL_MESSAGE_TYPE_PROXIMITY_PAIRING,
|
vendor = ProximityPairing.CONTINUITY_PROTOCOL_MESSAGE_TYPE_PROXIMITY_PAIRING,
|
||||||
length = ProximityPairing.PAIRING_MESSAGE_LENGTH.toUByte(),
|
length = ProximityPairing.PAIRING_MESSAGE_LENGTH.toUByte(),
|
||||||
device = (((data[1].toInt() and 255) shl 8) or (data[2].toInt() and 255)).toUShort(),
|
device = (((data[1].toInt() and 255) shl 8) or (data[2].toInt() and 255)).toUShort(),
|
||||||
@@ -70,6 +72,22 @@ abstract class ApplePodsFactory<PodType : ApplePods>(private val tag: String) {
|
|||||||
|
|
||||||
internal val knownDevices = mutableMapOf<PodDevice.Id, KnownDevice>()
|
internal val knownDevices = mutableMapOf<PodDevice.Id, KnownDevice>()
|
||||||
|
|
||||||
|
|
||||||
|
fun KnownDevice.getLatestCaseBattery(): Float? = history
|
||||||
|
.filterIsInstance<HasCase>()
|
||||||
|
.mapNotNull { it.batteryCasePercent }
|
||||||
|
.lastOrNull()
|
||||||
|
|
||||||
|
fun KnownDevice.getLatestCaseLidState(basic: DualAirPods): DualAirPods.LidState? {
|
||||||
|
val definitive = setOf(DualAirPods.LidState.OPEN, DualAirPods.LidState.CLOSED)
|
||||||
|
if (definitive.contains(basic.caseLidState)) return null
|
||||||
|
|
||||||
|
return history
|
||||||
|
.filterIsInstance<AirPodsPro>()
|
||||||
|
.lastOrNull { it.caseLidState != DualAirPods.LidState.NOT_IN_CASE }
|
||||||
|
?.caseLidState
|
||||||
|
}
|
||||||
|
|
||||||
internal open fun searchHistory(current: PodType): KnownDevice? {
|
internal open fun searchHistory(current: PodType): KnownDevice? {
|
||||||
val scanResult = current.scanResult
|
val scanResult = current.scanResult
|
||||||
val message = current.proximityMessage
|
val message = current.proximityMessage
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package eu.darken.capod.pods.core.apple
|
|
||||||
|
|
||||||
import eu.darken.capod.common.debug.logging.log
|
|
||||||
import eu.darken.capod.common.lowerNibble
|
|
||||||
import eu.darken.capod.pods.core.HasSinglePod
|
|
||||||
|
|
||||||
interface BasicSingleApplePods : ApplePods, HasSinglePod {
|
|
||||||
|
|
||||||
override val batteryHeadsetPercent: Float?
|
|
||||||
get() = when (val value = rawPodsBattery.lowerNibble.toInt()) {
|
|
||||||
15 -> null
|
|
||||||
else -> if (value > 10) {
|
|
||||||
log { "Left pod: Above 100% battery: $value" }
|
|
||||||
1.0f
|
|
||||||
} else {
|
|
||||||
(value / 10f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package eu.darken.capod.pods.core.apple
|
|
||||||
|
|
||||||
abstract class BasicSingleApplePodsFactory(private val tag: String) : ApplePodsFactory<BasicSingleApplePods>(tag)
|
|
||||||
+12
-36
@@ -7,12 +7,11 @@ import eu.darken.capod.common.debug.logging.log
|
|||||||
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.HasCase
|
import eu.darken.capod.pods.core.*
|
||||||
import eu.darken.capod.pods.core.HasDualEarDetection
|
import eu.darken.capod.pods.core.DualPodDevice.Pod
|
||||||
import eu.darken.capod.pods.core.HasDualPods
|
|
||||||
import eu.darken.capod.pods.core.HasDualPods.Pod
|
|
||||||
|
|
||||||
interface DualApplePods : ApplePods, HasDualPods, HasDualEarDetection, HasCase {
|
interface DualAirPods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasEarDetectionDual, HasCase,
|
||||||
|
HasStateDetection, HasDualMicrophone, HasAppleColor {
|
||||||
|
|
||||||
val primaryPod: Pod
|
val primaryPod: Pod
|
||||||
get() = when (rawStatus.isBitSet(5)) {
|
get() = when (rawStatus.isBitSet(5)) {
|
||||||
@@ -86,23 +85,23 @@ interface DualApplePods : ApplePods, HasDualPods, HasDualEarDetection, HasCase {
|
|||||||
* The data flip bit is set if the left pod is primary.
|
* The data flip bit is set if the left pod is primary.
|
||||||
* For the pod that is in the case, this is flipped again though.
|
* For the pod that is in the case, this is flipped again though.
|
||||||
*/
|
*/
|
||||||
val isLeftPodMicrophone: Boolean
|
override val isLeftPodMicrophone: Boolean
|
||||||
get() = rawStatus.isBitSet(5) xor isThisPodInThecase
|
get() = rawStatus.isBitSet(5) xor isThisPodInThecase
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data flip bit is UNset if the right pod is primary.
|
* The data flip bit is UNset if the right pod is primary.
|
||||||
* For the pod that is in the case, this is flipped again though.
|
* For the pod that is in the case, this is flipped again though.
|
||||||
*/
|
*/
|
||||||
val isRightPodMicrophone: Boolean
|
override val isRightPodMicrophone: Boolean
|
||||||
get() = !rawStatus.isBitSet(5) xor isThisPodInThecase
|
get() = !rawStatus.isBitSet(5) xor isThisPodInThecase
|
||||||
|
|
||||||
val isLeftPodCharging: Boolean
|
override val isLeftPodCharging: Boolean
|
||||||
get() = when (areValuesFlipped) {
|
get() = when (areValuesFlipped) {
|
||||||
false -> rawFlags.isBitSet(0)
|
false -> rawFlags.isBitSet(0)
|
||||||
true -> rawFlags.isBitSet(1)
|
true -> rawFlags.isBitSet(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isRightPodCharging: Boolean
|
override val isRightPodCharging: Boolean
|
||||||
get() = when (areValuesFlipped) {
|
get() = when (areValuesFlipped) {
|
||||||
false -> rawFlags.isBitSet(1)
|
false -> rawFlags.isBitSet(1)
|
||||||
true -> rawFlags.isBitSet(0)
|
true -> rawFlags.isBitSet(0)
|
||||||
@@ -143,35 +142,10 @@ interface DualApplePods : ApplePods, HasDualPods, HasDualEarDetection, HasCase {
|
|||||||
UNKNOWN(0xFF..0xFF);
|
UNKNOWN(0xFF..0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
val deviceColor: DeviceColor
|
override val state: ConnectionState
|
||||||
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
|
get() = ConnectionState.values().firstOrNull { rawSuffix == it.raw } ?: ConnectionState.UNKNOWN
|
||||||
|
|
||||||
fun getConnectionStateLabel(context: Context): String = context.getString(connectionState.labelRes)
|
enum class ConnectionState(val raw: UByte?, @StringRes val labelRes: Int) : HasStateDetection.State {
|
||||||
|
|
||||||
enum class ConnectionState(val raw: UByte?, @StringRes val labelRes: Int) {
|
|
||||||
DISCONNECTED(0x00, R.string.pods_connection_state_disconnected_label),
|
DISCONNECTED(0x00, R.string.pods_connection_state_disconnected_label),
|
||||||
IDLE(0x04, R.string.pods_connection_state_idle_label),
|
IDLE(0x04, R.string.pods_connection_state_idle_label),
|
||||||
MUSIC(0x05, R.string.pods_connection_state_music_label),
|
MUSIC(0x05, R.string.pods_connection_state_music_label),
|
||||||
@@ -180,6 +154,8 @@ interface DualApplePods : ApplePods, HasDualPods, HasDualEarDetection, HasCase {
|
|||||||
HANGING_UP(0x09, R.string.pods_connection_state_hanging_up_label),
|
HANGING_UP(0x09, R.string.pods_connection_state_hanging_up_label),
|
||||||
UNKNOWN(null, R.string.pods_connection_state_unknown_label);
|
UNKNOWN(null, R.string.pods_connection_state_unknown_label);
|
||||||
|
|
||||||
|
override fun getLabel(context: Context): String = context.getString(labelRes)
|
||||||
|
|
||||||
constructor(raw: Int, @StringRes labelRes: Int) : this(raw.toUByte(), labelRes)
|
constructor(raw: Int, @StringRes labelRes: Int) : this(raw.toUByte(), labelRes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,18 +3,17 @@ package eu.darken.capod.pods.core.apple
|
|||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.DEBUG
|
import eu.darken.capod.common.debug.logging.Logging.Priority.DEBUG
|
||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro
|
|
||||||
|
|
||||||
abstract class DualApplePodsFactory(private val tag: String) : ApplePodsFactory<DualApplePods>(tag) {
|
abstract class DualApplePodsFactory(private val tag: String) : ApplePodsFactory<DualAirPods>(tag) {
|
||||||
|
|
||||||
fun DualApplePods.getCaseMatchMarkings() = SplitPodsMarkings(
|
fun DualAirPods.getCaseMatchMarkings() = SplitPodsMarkings(
|
||||||
leftPodBattery = batteryLeftPodPercent,
|
leftPodBattery = batteryLeftPodPercent,
|
||||||
rightPodBattery = batteryRightPodPercent,
|
rightPodBattery = batteryRightPodPercent,
|
||||||
microPhoneLeft = isLeftPodMicrophone,
|
microPhoneLeft = isLeftPodMicrophone,
|
||||||
microPhoneRight = isRightPodMicrophone,
|
microPhoneRight = isRightPodMicrophone,
|
||||||
chargingLeft = isLeftPodCharging,
|
chargingLeft = isLeftPodCharging,
|
||||||
chargingRight = isRightPodCharging,
|
chargingRight = isRightPodCharging,
|
||||||
color = deviceColor,
|
color = rawDeviceColor,
|
||||||
model = model
|
model = model
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,36 +27,21 @@ abstract class DualApplePodsFactory(private val tag: String) : ApplePodsFactory<
|
|||||||
val microPhoneRight: Boolean,
|
val microPhoneRight: Boolean,
|
||||||
val chargingLeft: Boolean,
|
val chargingLeft: Boolean,
|
||||||
val chargingRight: Boolean,
|
val chargingRight: Boolean,
|
||||||
val color: DualApplePods.DeviceColor,
|
val color: UByte,
|
||||||
val model: PodDevice.Model,
|
val model: PodDevice.Model,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun Collection<KnownDevice>.findSplitPodsMatch(device: DualApplePods): Collection<KnownDevice> {
|
private fun Collection<KnownDevice>.findSplitPodsMatch(device: DualAirPods): Collection<KnownDevice> {
|
||||||
val target = device.getCaseMatchMarkings()
|
val target = device.getCaseMatchMarkings()
|
||||||
|
|
||||||
return filter { known ->
|
return filter { known ->
|
||||||
known.history
|
known.history
|
||||||
.filterIsInstance<DualApplePods>()
|
.filterIsInstance<DualAirPods>()
|
||||||
.any { it.getCaseMatchMarkings() == target }
|
.any { it.getCaseMatchMarkings() == target }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun KnownDevice.getLatestCaseBattery(): Float? = history
|
override fun searchHistory(current: DualAirPods): KnownDevice? {
|
||||||
.filterIsInstance<DualApplePods>()
|
|
||||||
.mapNotNull { it.batteryCasePercent }
|
|
||||||
.lastOrNull()
|
|
||||||
|
|
||||||
fun KnownDevice.getLatestCaseLidState(basic: DualApplePods): DualApplePods.LidState? {
|
|
||||||
val definitive = setOf(DualApplePods.LidState.OPEN, DualApplePods.LidState.CLOSED)
|
|
||||||
if (definitive.contains(basic.caseLidState)) return null
|
|
||||||
|
|
||||||
return history
|
|
||||||
.filterIsInstance<AirPodsPro>()
|
|
||||||
.lastOrNull { it.caseLidState != DualApplePods.LidState.NOT_IN_CASE }
|
|
||||||
?.caseLidState
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchHistory(current: DualApplePods): KnownDevice? {
|
|
||||||
val basicResult = super.searchHistory(current)
|
val basicResult = super.searchHistory(current)
|
||||||
|
|
||||||
val caseIgnored = knownDevices.values.findSplitPodsMatch(current)
|
val caseIgnored = knownDevices.values.findSplitPodsMatch(current)
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package eu.darken.capod.pods.core.apple
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import eu.darken.capod.pods.core.HasPodStyle
|
||||||
|
|
||||||
|
interface HasAppleColor : ApplePods, HasPodStyle {
|
||||||
|
|
||||||
|
override val podStyle: HasPodStyle.PodStyle
|
||||||
|
get() = DeviceColor.values()
|
||||||
|
.firstOrNull { it.raw == rawDeviceColor }
|
||||||
|
?: DeviceColor.UNKNOWN
|
||||||
|
|
||||||
|
enum class DeviceColor(val raw: UByte?) : HasPodStyle.PodStyle {
|
||||||
|
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);
|
||||||
|
|
||||||
|
override fun getLabel(context: Context): String = this.name
|
||||||
|
|
||||||
|
override fun getColor(context: Context): Int = android.R.color.white
|
||||||
|
|
||||||
|
override val identifier: String
|
||||||
|
get() = name
|
||||||
|
|
||||||
|
constructor(raw: Int) : this(raw.toUByte())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,22 @@
|
|||||||
package eu.darken.capod.pods.core.apple
|
package eu.darken.capod.pods.core.apple
|
||||||
|
|
||||||
import eu.darken.capod.common.isBitSet
|
import eu.darken.capod.common.debug.logging.log
|
||||||
import eu.darken.capod.pods.core.HasEarDetection
|
import eu.darken.capod.common.lowerNibble
|
||||||
import eu.darken.capod.pods.core.HasSinglePod
|
import eu.darken.capod.pods.core.SinglePodDevice
|
||||||
|
|
||||||
interface SingleApplePods : BasicSingleApplePods, HasEarDetection, HasSinglePod {
|
/**
|
||||||
|
* Devices that only present a single charge level, e.g. most Beats devices
|
||||||
|
*/
|
||||||
|
interface SingleApplePods : ApplePods, SinglePodDevice, HasAppleColor {
|
||||||
|
|
||||||
val isHeadphonesBeingWorn: Boolean
|
override val batteryHeadsetPercent: Float?
|
||||||
get() = rawStatus.isBitSet(1)
|
get() = when (val value = rawPodsBattery.lowerNibble.toInt()) {
|
||||||
|
15 -> null
|
||||||
val isHeadsetBeingCharged: Boolean
|
else -> if (value > 10) {
|
||||||
get() = rawFlags.isBitSet(0)
|
log { "Headset above 100% battery: $value" }
|
||||||
|
1.0f
|
||||||
override val isBeingWorn: Boolean
|
} else {
|
||||||
get() = isHeadphonesBeingWorn
|
(value / 10f)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ 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
|
||||||
import eu.darken.capod.pods.core.apple.ApplePods
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -20,15 +20,15 @@ data class AirPodsGen1 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,
|
||||||
private val cachedCaseState: DualApplePods.LidState? = null
|
private val cachedCaseState: DualAirPods.LidState? = null
|
||||||
) : DualApplePods {
|
) : DualAirPods {
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN1
|
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN1
|
||||||
|
|
||||||
override val batteryCasePercent: Float?
|
override val batteryCasePercent: Float?
|
||||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||||
|
|
||||||
override val caseLidState: DualApplePods.LidState
|
override val caseLidState: DualAirPods.LidState
|
||||||
get() = cachedCaseState ?: super.caseLidState
|
get() = cachedCaseState ?: super.caseLidState
|
||||||
|
|
||||||
override val rssi: Int
|
override val rssi: Int
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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
|
||||||
import eu.darken.capod.pods.core.apple.ApplePods
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -20,15 +20,15 @@ data class AirPodsGen2 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,
|
||||||
private val cachedCaseState: DualApplePods.LidState? = null
|
private val cachedCaseState: DualAirPods.LidState? = null
|
||||||
) : DualApplePods {
|
) : DualAirPods {
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN2
|
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN2
|
||||||
|
|
||||||
override val batteryCasePercent: Float?
|
override val batteryCasePercent: Float?
|
||||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||||
|
|
||||||
override val caseLidState: DualApplePods.LidState
|
override val caseLidState: DualAirPods.LidState
|
||||||
get() = cachedCaseState ?: super.caseLidState
|
get() = cachedCaseState ?: super.caseLidState
|
||||||
|
|
||||||
override val rssi: Int
|
override val rssi: Int
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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
|
||||||
import eu.darken.capod.pods.core.apple.ApplePods
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -20,14 +20,14 @@ data class AirPodsGen3 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,
|
||||||
private val cachedCaseState: DualApplePods.LidState? = null
|
private val cachedCaseState: DualAirPods.LidState? = null
|
||||||
) : DualApplePods {
|
) : DualAirPods {
|
||||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN3
|
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN3
|
||||||
|
|
||||||
override val batteryCasePercent: Float?
|
override val batteryCasePercent: Float?
|
||||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||||
|
|
||||||
override val caseLidState: DualApplePods.LidState
|
override val caseLidState: DualAirPods.LidState
|
||||||
get() = cachedCaseState ?: super.caseLidState
|
get() = cachedCaseState ?: super.caseLidState
|
||||||
|
|
||||||
override val rssi: Int
|
override val rssi: Int
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package eu.darken.capod.pods.core.apple.airpods
|
|||||||
|
|
||||||
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.common.isBitSet
|
||||||
|
import eu.darken.capod.pods.core.HasEarDetection
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
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.SingleApplePods
|
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||||
@@ -19,13 +21,22 @@ data class AirPodsMax(
|
|||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : SingleApplePods {
|
) : SingleApplePods, HasEarDetection {
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_MAX
|
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_MAX
|
||||||
|
|
||||||
override val rssi: Int
|
override val rssi: Int
|
||||||
get() = rssiAverage ?: super.rssi
|
get() = rssiAverage ?: super.rssi
|
||||||
|
|
||||||
|
val isHeadphonesBeingWorn: Boolean
|
||||||
|
get() = rawStatus.isBitSet(1)
|
||||||
|
|
||||||
|
val isHeadsetBeingCharged: Boolean
|
||||||
|
get() = rawFlags.isBitSet(0)
|
||||||
|
|
||||||
|
override val isBeingWorn: Boolean
|
||||||
|
get() = isHeadphonesBeingWorn
|
||||||
|
|
||||||
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||||
|
|
||||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||||
@@ -49,7 +60,6 @@ data class AirPodsMax(
|
|||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.averageRssi(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ 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
|
||||||
import eu.darken.capod.pods.core.apple.ApplePods
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods.LidState
|
import eu.darken.capod.pods.core.apple.DualAirPods.LidState
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -22,7 +22,7 @@ data class AirPodsPro(
|
|||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
private val cachedBatteryPercentage: Float? = null,
|
private val cachedBatteryPercentage: Float? = null,
|
||||||
private val cachedCaseState: LidState? = null
|
private val cachedCaseState: LidState? = null
|
||||||
) : DualApplePods {
|
) : DualAirPods {
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_PRO
|
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_PRO
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ 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
|
||||||
import eu.darken.capod.pods.core.apple.ApplePods
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePodsFactory
|
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -19,14 +19,14 @@ data class BeatsFlex(
|
|||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : BasicSingleApplePods {
|
) : SingleApplePods {
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.BEATS_FLEX
|
override val model: PodDevice.Model = PodDevice.Model.BEATS_FLEX
|
||||||
|
|
||||||
override val rssi: Int
|
override val rssi: Int
|
||||||
get() = rssiAverage ?: super.rssi
|
get() = rssiAverage ?: super.rssi
|
||||||
|
|
||||||
class Factory @Inject constructor() : BasicSingleApplePodsFactory(TAG) {
|
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||||
|
|
||||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ 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
|
||||||
import eu.darken.capod.pods.core.apple.ApplePods
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePodsFactory
|
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -19,13 +19,13 @@ data class BeatsSolo3(
|
|||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : BasicSingleApplePods {
|
) : SingleApplePods {
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.BEATS_SOLO_3
|
override val model: PodDevice.Model = PodDevice.Model.BEATS_SOLO_3
|
||||||
override val rssi: Int
|
override val rssi: Int
|
||||||
get() = rssiAverage ?: super.rssi
|
get() = rssiAverage ?: super.rssi
|
||||||
|
|
||||||
class Factory @Inject constructor() : BasicSingleApplePodsFactory(TAG) {
|
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||||
|
|
||||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ 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
|
||||||
import eu.darken.capod.pods.core.apple.ApplePods
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePodsFactory
|
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -19,11 +19,11 @@ data class BeatsStudio3(
|
|||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : BasicSingleApplePods {
|
) : SingleApplePods {
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.BEATS_STUDIO_3
|
override val model: PodDevice.Model = PodDevice.Model.BEATS_STUDIO_3
|
||||||
|
|
||||||
class Factory @Inject constructor() : BasicSingleApplePodsFactory(TAG) {
|
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||||
|
|
||||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||||
getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||||
@@ -46,7 +46,6 @@ data class BeatsStudio3(
|
|||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.averageRssi(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ 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
|
||||||
import eu.darken.capod.pods.core.apple.ApplePods
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePodsFactory
|
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -19,14 +19,14 @@ data class BeatsX(
|
|||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : BasicSingleApplePods {
|
) : SingleApplePods {
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.BEATS_X
|
override val model: PodDevice.Model = PodDevice.Model.BEATS_X
|
||||||
|
|
||||||
override val rssi: Int
|
override val rssi: Int
|
||||||
get() = rssiAverage ?: super.rssi
|
get() = rssiAverage ?: super.rssi
|
||||||
|
|
||||||
class Factory @Inject constructor() : BasicSingleApplePodsFactory(TAG) {
|
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||||
|
|
||||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ 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
|
||||||
import eu.darken.capod.pods.core.apple.ApplePods
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePodsFactory
|
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -19,13 +19,14 @@ data class PowerBeats3(
|
|||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : BasicSingleApplePods {
|
) : SingleApplePods {
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_3
|
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_3
|
||||||
|
|
||||||
override val rssi: Int
|
override val rssi: Int
|
||||||
get() = rssiAverage ?: super.rssi
|
get() = rssiAverage ?: super.rssi
|
||||||
|
|
||||||
class Factory @Inject constructor() : BasicSingleApplePodsFactory(TAG) {
|
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||||
|
|
||||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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
|
||||||
import eu.darken.capod.pods.core.apple.ApplePods
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -20,15 +20,15 @@ data class PowerBeatsPro(
|
|||||||
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,
|
||||||
private val cachedCaseState: DualApplePods.LidState? = null
|
private val cachedCaseState: DualAirPods.LidState? = null
|
||||||
) : DualApplePods {
|
) : DualAirPods {
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_PRO
|
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_PRO
|
||||||
|
|
||||||
override val batteryCasePercent: Float?
|
override val batteryCasePercent: Float?
|
||||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||||
|
|
||||||
override val caseLidState: DualApplePods.LidState
|
override val caseLidState: DualAirPods.LidState
|
||||||
get() = cachedCaseState ?: super.caseLidState
|
get() = cachedCaseState ?: super.caseLidState
|
||||||
|
|
||||||
override val rssi: Int
|
override val rssi: Int
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
package eu.darken.capod.pods.core.apple.fakes
|
|
||||||
|
|
||||||
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.DualApplePodsFactory
|
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
|
||||||
import java.time.Instant
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basically an AirPods GEN1 clone
|
|
||||||
*/
|
|
||||||
data class Twsi99999 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: DualApplePods.LidState? = null
|
|
||||||
) : DualApplePods {
|
|
||||||
|
|
||||||
override val model: PodDevice.Model = PodDevice.Model.TWS_I99999
|
|
||||||
|
|
||||||
override val batteryCasePercent: Float?
|
|
||||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
|
||||||
|
|
||||||
override val caseLidState: DualApplePods.LidState
|
|
||||||
get() = cachedCaseState ?: super.caseLidState
|
|
||||||
|
|
||||||
override val rssi: Int
|
|
||||||
get() = rssiAverage ?: super.rssi
|
|
||||||
|
|
||||||
class Factory @Inject constructor() : DualApplePodsFactory(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 = Twsi99999(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,
|
|
||||||
seenCounter = result.seenCounter,
|
|
||||||
confidence = result.confidence,
|
|
||||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
|
||||||
cachedCaseState = result.getLatestCaseLidState(basic)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val DEVICE_CODE = 0x0220.toUShort()
|
|
||||||
private val TAG = logTag("PodDevice", "Apple", "TWS", "i99999")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package eu.darken.capod.pods.core.apple.misc
|
||||||
|
|
||||||
|
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.DualPodDevice
|
||||||
|
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.ApplePodsFactory
|
||||||
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
|
import java.time.Instant
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basically an AirPods GEN1 clone
|
||||||
|
* Similar data structure but a lot of placeholder values or hardcoded values
|
||||||
|
*/
|
||||||
|
data class Twsi99999 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,
|
||||||
|
) : ApplePods, DualPodDevice, HasDualMicrophone, HasCase {
|
||||||
|
|
||||||
|
override val model: PodDevice.Model = PodDevice.Model.TWS_I99999
|
||||||
|
|
||||||
|
override val rssi: Int
|
||||||
|
get() = rssiAverage ?: super<ApplePods>.rssi
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normally values for the left pod are in the lower nibbles, if the left pod is primary (microphone)
|
||||||
|
* If the right pod is the primary, the values are flipped.
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isThisPodInThecase: Boolean
|
||||||
|
get() = rawStatus.isBitSet(6)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data flip bit is set if the left pod is primary.
|
||||||
|
* For the pod that is in the case, this is flipped again though.
|
||||||
|
*/
|
||||||
|
override val isLeftPodMicrophone: Boolean
|
||||||
|
get() = rawStatus.isBitSet(5) xor isThisPodInThecase
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data flip bit is UNset if the right pod is primary.
|
||||||
|
* For the pod that is in the case, this is flipped again though.
|
||||||
|
*/
|
||||||
|
override val isRightPodMicrophone: Boolean
|
||||||
|
get() = !rawStatus.isBitSet(5) xor isThisPodInThecase
|
||||||
|
|
||||||
|
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<Twsi99999>(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 = Twsi99999(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,
|
||||||
|
seenCounter = result.seenCounter,
|
||||||
|
confidence = result.confidence,
|
||||||
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
|
rssiAverage = result.averageRssi(basic.rssi),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val DEVICE_CODE = 0x0220.toUShort()
|
||||||
|
private val TAG = logTag("PodDevice", "Apple", "TWS", "i99999")
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -1,10 +1,12 @@
|
|||||||
package eu.darken.capod.pods.core.apple
|
package eu.darken.capod.pods.core.apple.misc
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
import eu.darken.capod.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
|
||||||
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
|
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
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package eu.darken.capod.pods.core.apple.misc
|
||||||
|
|
||||||
|
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.DualPodDevice
|
||||||
|
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.ApplePodsFactory
|
||||||
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
|
import java.time.Instant
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AirPods Pro clone similar to Twsi999999.
|
||||||
|
* Shorter data structure.
|
||||||
|
*/
|
||||||
|
data class VarunrAirPodsPro 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,
|
||||||
|
) : ApplePods, DualPodDevice, HasDualMicrophone, HasCase {
|
||||||
|
|
||||||
|
override val model: PodDevice.Model = PodDevice.Model.VARUNR_AIRPODS_PRO
|
||||||
|
|
||||||
|
override val rssi: Int
|
||||||
|
get() = rssiAverage ?: super<ApplePods>.rssi
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normally values for the left pod are in the lower nibbles, if the left pod is primary (microphone)
|
||||||
|
* If the right pod is the primary, the values are flipped.
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isThisPodInThecase: Boolean
|
||||||
|
get() = rawStatus.isBitSet(6)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data flip bit is set if the left pod is primary.
|
||||||
|
* For the pod that is in the case, this is flipped again though.
|
||||||
|
*/
|
||||||
|
override val isLeftPodMicrophone: Boolean
|
||||||
|
get() = rawStatus.isBitSet(5) xor isThisPodInThecase
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data flip bit is UNset if the right pod is primary.
|
||||||
|
* For the pod that is in the case, this is flipped again though.
|
||||||
|
*/
|
||||||
|
override val isRightPodMicrophone: Boolean
|
||||||
|
get() = !rawStatus.isBitSet(5) xor isThisPodInThecase
|
||||||
|
|
||||||
|
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<VarunrAirPodsPro>(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 = VarunrAirPodsPro(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,
|
||||||
|
seenCounter = result.seenCounter,
|
||||||
|
confidence = result.confidence,
|
||||||
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
|
rssiAverage = result.averageRssi(basic.rssi),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val DEVICE_CODE = 0x0E20.toUShort()
|
||||||
|
private val TAG = logTag("PodDevice", "Apple", "Varunr", "AirPodsPro")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,9 +8,9 @@ import eu.darken.capod.common.debug.logging.logTag
|
|||||||
import eu.darken.capod.common.flow.setupCommonEventHandlers
|
import eu.darken.capod.common.flow.setupCommonEventHandlers
|
||||||
import eu.darken.capod.main.core.GeneralSettings
|
import eu.darken.capod.main.core.GeneralSettings
|
||||||
import eu.darken.capod.monitor.core.PodMonitor
|
import eu.darken.capod.monitor.core.PodMonitor
|
||||||
import eu.darken.capod.pods.core.HasDualEarDetection
|
|
||||||
import eu.darken.capod.pods.core.HasEarDetection
|
import eu.darken.capod.pods.core.HasEarDetection
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.HasEarDetectionDual
|
||||||
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
import eu.darken.capod.reaction.settings.ReactionSettings
|
import eu.darken.capod.reaction.settings.ReactionSettings
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -68,12 +68,12 @@ class AutoConnect @Inject constructor(
|
|||||||
val conditionFulfilled = when (condition) {
|
val conditionFulfilled = when (condition) {
|
||||||
AutoConnectCondition.WHEN_SEEN -> true
|
AutoConnectCondition.WHEN_SEEN -> true
|
||||||
AutoConnectCondition.CASE_OPEN -> when (mainDevice) {
|
AutoConnectCondition.CASE_OPEN -> when (mainDevice) {
|
||||||
is DualApplePods -> mainDevice.caseLidState == DualApplePods.LidState.OPEN
|
is DualAirPods -> mainDevice.caseLidState == DualAirPods.LidState.OPEN
|
||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
AutoConnectCondition.IN_EAR -> when (mainDevice) {
|
AutoConnectCondition.IN_EAR -> when (mainDevice) {
|
||||||
is HasEarDetection -> {
|
is HasEarDetection -> {
|
||||||
if (mainDevice is HasDualEarDetection && reactionSettings.onePodMode.value) {
|
if (mainDevice is HasEarDetectionDual && reactionSettings.onePodMode.value) {
|
||||||
mainDevice.isEitherPodInEar
|
mainDevice.isEitherPodInEar
|
||||||
} else {
|
} else {
|
||||||
mainDevice.isBeingWorn
|
mainDevice.isBeingWorn
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import eu.darken.capod.common.debug.logging.logTag
|
|||||||
import eu.darken.capod.common.flow.setupCommonEventHandlers
|
import eu.darken.capod.common.flow.setupCommonEventHandlers
|
||||||
import eu.darken.capod.common.flow.withPrevious
|
import eu.darken.capod.common.flow.withPrevious
|
||||||
import eu.darken.capod.monitor.core.PodMonitor
|
import eu.darken.capod.monitor.core.PodMonitor
|
||||||
import eu.darken.capod.pods.core.HasDualEarDetection
|
|
||||||
import eu.darken.capod.pods.core.HasEarDetection
|
import eu.darken.capod.pods.core.HasEarDetection
|
||||||
|
import eu.darken.capod.pods.core.HasEarDetectionDual
|
||||||
import eu.darken.capod.reaction.settings.ReactionSettings
|
import eu.darken.capod.reaction.settings.ReactionSettings
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -48,7 +48,7 @@ class PlayPause @Inject constructor(
|
|||||||
return@onEach
|
return@onEach
|
||||||
}
|
}
|
||||||
|
|
||||||
if (previous is HasDualEarDetection && current is HasDualEarDetection && reactionSettings.onePodMode.value) {
|
if (previous is HasEarDetectionDual && current is HasEarDetectionDual && reactionSettings.onePodMode.value) {
|
||||||
log(TAG) { "Ear status changed for dual pod device in single pod mode." }
|
log(TAG) { "Ear status changed for dual pod device in single pod mode." }
|
||||||
val previousWorn = previous.isEitherPodInEar
|
val previousWorn = previous.isEitherPodInEar
|
||||||
val currentWorn = current.isEitherPodInEar
|
val currentWorn = current.isEitherPodInEar
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import eu.darken.capod.common.flow.setupCommonEventHandlers
|
|||||||
import eu.darken.capod.common.flow.withPrevious
|
import eu.darken.capod.common.flow.withPrevious
|
||||||
import eu.darken.capod.monitor.core.PodMonitor
|
import eu.darken.capod.monitor.core.PodMonitor
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
import eu.darken.capod.reaction.popup.ui.PopUpWindow
|
import eu.darken.capod.reaction.popup.ui.PopUpWindow
|
||||||
import eu.darken.capod.reaction.settings.ReactionSettings
|
import eu.darken.capod.reaction.settings.ReactionSettings
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
@@ -39,7 +39,7 @@ class PopUpReaction @Inject constructor(
|
|||||||
.withPrevious()
|
.withPrevious()
|
||||||
.setupCommonEventHandlers(TAG) { "monitor" }
|
.setupCommonEventHandlers(TAG) { "monitor" }
|
||||||
.map { (previous, current) ->
|
.map { (previous, current) ->
|
||||||
if (previous is DualApplePods? && current is DualApplePods) {
|
if (previous is DualAirPods? && current is DualAirPods) {
|
||||||
log(TAG, VERBOSE) {
|
log(TAG, VERBOSE) {
|
||||||
val prev = previous?.rawCaseLidState?.let { String.format("%02X", it.toByte()) }
|
val prev = previous?.rawCaseLidState?.let { String.format("%02X", it.toByte()) }
|
||||||
val cur = current.rawCaseLidState.let { String.format("%02X", it.toByte()) }
|
val cur = current.rawCaseLidState.let { String.format("%02X", it.toByte()) }
|
||||||
@@ -60,8 +60,8 @@ class PopUpReaction @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun tryPopWindow(current: DualApplePods) {
|
private suspend fun tryPopWindow(current: DualAirPods) {
|
||||||
if (current.caseLidState == DualApplePods.LidState.OPEN) {
|
if (current.caseLidState == DualAirPods.LidState.OPEN) {
|
||||||
log(TAG, INFO) { "Show popup" }
|
log(TAG, INFO) { "Show popup" }
|
||||||
|
|
||||||
val now = Instant.now()
|
val now = Instant.now()
|
||||||
@@ -78,9 +78,9 @@ class PopUpReaction @Inject constructor(
|
|||||||
withContext(dispatcherProvider.Main) {
|
withContext(dispatcherProvider.Main) {
|
||||||
popupWindow.show(current)
|
popupWindow.show(current)
|
||||||
}
|
}
|
||||||
} else if (current.caseLidState != DualApplePods.LidState.OPEN) {
|
} else if (current.caseLidState != DualAirPods.LidState.OPEN) {
|
||||||
when (current.caseLidState) {
|
when (current.caseLidState) {
|
||||||
DualApplePods.LidState.CLOSED -> {
|
DualAirPods.LidState.CLOSED -> {
|
||||||
log(TAG, INFO) { "Lid was actively closed, resetting cooldown." }
|
log(TAG, INFO) { "Lid was actively closed, resetting cooldown." }
|
||||||
coolDowns.remove(current.identifier)
|
coolDowns.remove(current.identifier)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,9 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
||||||
import eu.darken.capod.databinding.PopupNotificationDualPodsBinding
|
import eu.darken.capod.databinding.PopupNotificationDualPodsBinding
|
||||||
import eu.darken.capod.databinding.PopupNotificationSinglePodsBasicBinding
|
|
||||||
import eu.darken.capod.databinding.PopupNotificationSinglePodsBinding
|
import eu.darken.capod.databinding.PopupNotificationSinglePodsBinding
|
||||||
import eu.darken.capod.pods.core.*
|
import eu.darken.capod.pods.core.*
|
||||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
|
||||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -28,16 +26,13 @@ class PopUpPodViewFactory @Inject constructor(
|
|||||||
private val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
private val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||||
|
|
||||||
fun createContentView(parent: ViewGroup, device: PodDevice): View = when (device) {
|
fun createContentView(parent: ViewGroup, device: PodDevice): View = when (device) {
|
||||||
is DualApplePods -> createDualApplePods(parent, device)
|
is DualAirPods -> createDualApplePods(parent, device)
|
||||||
is SingleApplePods -> createSingleApplePods(parent, device) // Unused, has no case to trigger reaction?
|
// Unused, has no case to trigger reaction?
|
||||||
is BasicSingleApplePods -> createSingleBasicApplePods(
|
is SingleApplePods -> createSingleApplePods(parent, device)
|
||||||
parent,
|
|
||||||
device
|
|
||||||
) // Unused, has no case to trigger reaction?
|
|
||||||
else -> throw IllegalArgumentException("Unexpected device: $device")
|
else -> throw IllegalArgumentException("Unexpected device: $device")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDualApplePods(parent: ViewGroup, device: DualApplePods): View =
|
private fun createDualApplePods(parent: ViewGroup, device: DualAirPods): View =
|
||||||
PopupNotificationDualPodsBinding.inflate(layoutInflater, parent, false).apply {
|
PopupNotificationDualPodsBinding.inflate(layoutInflater, parent, false).apply {
|
||||||
device.apply {
|
device.apply {
|
||||||
podIcon.setImageResource(iconRes)
|
podIcon.setImageResource(iconRes)
|
||||||
@@ -72,17 +67,4 @@ class PopUpPodViewFactory @Inject constructor(
|
|||||||
}
|
}
|
||||||
}.root
|
}.root
|
||||||
|
|
||||||
private fun createSingleBasicApplePods(parent: ViewGroup, device: BasicSingleApplePods): View =
|
|
||||||
PopupNotificationSinglePodsBasicBinding.inflate(layoutInflater, parent, false).apply {
|
|
||||||
device.apply {
|
|
||||||
headphonesIcon.setImageResource(iconRes)
|
|
||||||
headphonesLabel.text = getLabel(context)
|
|
||||||
signal.text = getSignalQuality(context)
|
|
||||||
signal.isInvisible = debugSettings.isDebugModeEnabled.value
|
|
||||||
|
|
||||||
headphonesBatteryIcon.setImageResource(getBatteryDrawable(batteryHeadsetPercent))
|
|
||||||
headphonesBatteryLabel.text = getBatteryLevelHeadset(context)
|
|
||||||
}
|
|
||||||
}.root
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
tools:listitem="@layout/overview_pods_apple_dual_item"
|
tools:listitem="@layout/overview_pods_dual_item"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/headphones_label"
|
|
||||||
style="@style/TextAppearance.Compat.Notification.Title"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
tools:text="Beats Solo 3" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="8dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/headphones_battery_icon"
|
|
||||||
style="@style/PodInfoItemIcon"
|
|
||||||
android:src="@drawable/ic_baseline_battery_unknown_24" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/headphones_battery_label"
|
|
||||||
style="@style/TextAppearance.Compat.Notification.Title"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginHorizontal="8dp"
|
|
||||||
tools:text="100%" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/card"
|
|
||||||
style="@style/MyCardView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginVertical="8dp"
|
|
||||||
tools:context=".main.ui.MainActivity">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:id="@+id/card_content"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/device_icon"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/name"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/name"
|
|
||||||
app:srcCompat="@drawable/ic_device_generic_headphones" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/name"
|
|
||||||
style="@style/TextAppearance.MaterialComponents.Body2"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/last_seen"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/reception"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/device_icon"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:text="Power Beats 3" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/last_seen"
|
|
||||||
style="@style/TextAppearance.MaterialComponents.Caption"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/barrier_top"
|
|
||||||
app:layout_constraintEnd_toEndOf="@id/name"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/name"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/name"
|
|
||||||
tools:text="3s ago" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/reception"
|
|
||||||
style="@style/TextAppearance.MaterialComponents.Caption"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:gravity="center"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/name"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/reception_icon"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/name"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/name"
|
|
||||||
tools:text="Yours (RSSI -61)" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/reception_icon"
|
|
||||||
style="@style/TextAppearance.MaterialComponents.Caption"
|
|
||||||
android:layout_width="16dp"
|
|
||||||
android:layout_height="16dp"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:gravity="center"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/reception"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/reception"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/reception"
|
|
||||||
app:srcCompat="@drawable/ic_baseline_settings_input_antenna_24"
|
|
||||||
tools:text="Yours (RSSI -61)" />
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Barrier
|
|
||||||
android:id="@+id/barrier_top"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:barrierDirection="top"
|
|
||||||
app:constraint_referenced_ids="headphones"
|
|
||||||
tools:layout_editor_absoluteY="60dp" />
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:id="@+id/headphones"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/barrier_bottom"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/barrier_top"
|
|
||||||
app:layout_constraintVertical_bias="0.0"
|
|
||||||
app:layout_goneMarginBottom="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/battery_icon"
|
|
||||||
style="@style/PodInfoItemIcon"
|
|
||||||
android:src="@drawable/ic_baseline_battery_unknown_24"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/battery_label"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/battery_label" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/battery_label"
|
|
||||||
style="@style/PodInfoItemText"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:text="@string/general_value_not_available_label"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/battery_icon"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Barrier
|
|
||||||
android:id="@+id/barrier_bottom"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:barrierDirection="bottom"
|
|
||||||
app:constraint_referenced_ids="headphones" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/status"
|
|
||||||
style="@style/TextAppearance.MaterialComponents.Body2"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/barrier_bottom"
|
|
||||||
tools:text="Music Active, Case Closed" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
+4
-4
@@ -12,7 +12,8 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/card_content"
|
android:id="@+id/card_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/device_icon"
|
android:id="@+id/device_icon"
|
||||||
@@ -107,7 +108,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:minHeight="108dp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/barrier_bottom"
|
app:layout_constraintBottom_toTopOf="@id/barrier_bottom"
|
||||||
app:layout_constraintEnd_toStartOf="@id/pod_case_container"
|
app:layout_constraintEnd_toStartOf="@id/pod_case_container"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -117,6 +117,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/pod_left_icon"
|
android:id="@+id/pod_left_icon"
|
||||||
style="@style/PodInfoItemIcon"
|
style="@style/PodInfoItemIcon"
|
||||||
|
android:contentDescription="@string/pods_dual_left_label"
|
||||||
android:src="@drawable/ic_airpod_left_24"
|
android:src="@drawable/ic_airpod_left_24"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/pod_left_label"
|
app:layout_constraintBottom_toBottomOf="@id/pod_left_label"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -322,6 +323,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/pod_right_icon"
|
android:id="@+id/pod_right_icon"
|
||||||
style="@style/PodInfoItemIcon"
|
style="@style/PodInfoItemIcon"
|
||||||
|
android:contentDescription="@string/pods_dual_right_label"
|
||||||
android:src="@drawable/ic_airpod_right_24"
|
android:src="@drawable/ic_airpod_right_24"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/pod_right_label"
|
app:layout_constraintBottom_toBottomOf="@id/pod_right_label"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -420,7 +422,6 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@id/pod_right_microphone_label" />
|
app:layout_constraintTop_toBottomOf="@id/pod_right_microphone_label" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Barrier
|
<androidx.constraintlayout.widget.Barrier
|
||||||
android:id="@+id/barrier_bottom"
|
android:id="@+id/barrier_bottom"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -435,7 +436,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
+3
-5
@@ -12,7 +12,8 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/card_content"
|
android:id="@+id/card_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/device_icon"
|
android:id="@+id/device_icon"
|
||||||
@@ -96,13 +97,11 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:minHeight="64dp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/barrier_bottom"
|
app:layout_constraintBottom_toTopOf="@id/barrier_bottom"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/barrier_top"
|
app:layout_constraintTop_toBottomOf="@id/barrier_top"
|
||||||
app:layout_constraintVertical_bias="0.0"
|
app:layout_constraintVertical_bias="0.0">
|
||||||
app:layout_goneMarginBottom="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/battery_icon"
|
android:id="@+id/battery_icon"
|
||||||
@@ -180,7 +179,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/headphones_icon"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/ic_device_generic_earbuds"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/headphones_label"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/headphones_label"
|
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/headphones_label" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/headphones_label"
|
|
||||||
style="@style/TextAppearance.MaterialComponents.Body1"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginHorizontal="8dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/signal"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/headphones_icon"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:text="Beats Solo 3" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/signal"
|
|
||||||
style="@style/TextAppearance.MaterialComponents.Caption"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:drawableEnd="@drawable/ic_baseline_signal_cellular_alt_24"
|
|
||||||
android:gravity="center"
|
|
||||||
android:minWidth="44dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/headphones_label"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/headphones_label"
|
|
||||||
tools:text="-90%" />
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:id="@+id/headphones_container"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintHorizontal_chainStyle="spread"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/headphones_label">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@id/headphones_battery_icon"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/ic_baseline_battery_unknown_24"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/headphones_battery_label"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/headphones_battery_label"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/headphones_battery_label" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/headphones_battery_label"
|
|
||||||
style="@style/TextAppearance.MaterialComponents.Body1"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/headphones_battery_icon"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:text="100%" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -81,6 +81,7 @@
|
|||||||
<string name="settings_monitor_mode_label">Mode surveillance</string>
|
<string name="settings_monitor_mode_label">Mode surveillance</string>
|
||||||
<string name="settings_monitor_mode_description">Les circonstances de surveillance des données Bluetooth par l’appli.</string>
|
<string name="settings_monitor_mode_description">Les circonstances de surveillance des données Bluetooth par l’appli.</string>
|
||||||
<string name="settings_scanner_mode_label">Mode analyse</string>
|
<string name="settings_scanner_mode_label">Mode analyse</string>
|
||||||
|
<string name="settings_scanner_mode_description">L’analyseur des données « basse énergie » de Bluetooth devrait-il prioriser les performances ou préserver l’énergie ?</string>
|
||||||
<string name="settings_monitor_mode_manual_label">Si l’appli est en fonction</string>
|
<string name="settings_monitor_mode_manual_label">Si l’appli est en fonction</string>
|
||||||
<string name="settings_monitor_mode_automatic_label">Si l’appareil est connecté</string>
|
<string name="settings_monitor_mode_automatic_label">Si l’appareil est connecté</string>
|
||||||
<string name="settings_monitor_mode_always_label">Toujours</string>
|
<string name="settings_monitor_mode_always_label">Toujours</string>
|
||||||
@@ -88,10 +89,13 @@
|
|||||||
<string name="settings_scanner_mode_balanced_label">Équilibré</string>
|
<string name="settings_scanner_mode_balanced_label">Équilibré</string>
|
||||||
<string name="settings_scanner_mode_lowlatency_label">Faible latence</string>
|
<string name="settings_scanner_mode_lowlatency_label">Faible latence</string>
|
||||||
<string name="settings_autopause_label">Pause automatique</string>
|
<string name="settings_autopause_label">Pause automatique</string>
|
||||||
|
<string name="settings_autopause_description">Mettre le son en pause si vous ôtez l’appareil de votre oreille.</string>
|
||||||
<string name="settings_showall_label">Afficher tous les appareils</string>
|
<string name="settings_showall_label">Afficher tous les appareils</string>
|
||||||
<string name="settings_showall_description">Afficher les appareils d’autres personnes à proximité.</string>
|
<string name="settings_showall_description">Afficher les appareils d’autres personnes à proximité.</string>
|
||||||
<string name="settings_autopplay_label">Lecture automatique</string>
|
<string name="settings_autopplay_label">Lecture automatique</string>
|
||||||
|
<string name="settings_autoplay_description">Démarrer la lecture du son quand l’appareil est porté.</string>
|
||||||
<string name="settings_fake_data_label">Fausses données</string>
|
<string name="settings_fake_data_label">Fausses données</string>
|
||||||
|
<string name="settings_fake_data_description">Afficher les fausses données, c.-à-d. simuler les appareils qui n’existent pas.</string>
|
||||||
<string name="settings_debug_label">Paramètres de débogage</string>
|
<string name="settings_debug_label">Paramètres de débogage</string>
|
||||||
<string name="settings_debug_description">Autres paramètres pour aider à diagnostiquer les problèmes de l’appli.</string>
|
<string name="settings_debug_description">Autres paramètres pour aider à diagnostiquer les problèmes de l’appli.</string>
|
||||||
<string name="settings_signal_minimum_label">Qualité minimale du signal</string>
|
<string name="settings_signal_minimum_label">Qualité minimale du signal</string>
|
||||||
@@ -118,6 +122,7 @@
|
|||||||
<string name="overview_bluetooth_disabled_label">Le Bluetooth est désactivé</string>
|
<string name="overview_bluetooth_disabled_label">Le Bluetooth est désactivé</string>
|
||||||
<string name="overview_bluetooth_disabled_description">Le Bluetooth est désactivé, activez-le ;)</string>
|
<string name="overview_bluetooth_disabled_description">Le Bluetooth est désactivé, activez-le ;)</string>
|
||||||
<string name="help_translate_label">Traduction</string>
|
<string name="help_translate_label">Traduction</string>
|
||||||
|
<string name="help_translate_description">Aider à traduire cette appli dans votre langue préférée.</string>
|
||||||
<string name="settings_onepod_mode_label">Mode à un seul AirPod</string>
|
<string name="settings_onepod_mode_label">Mode à un seul AirPod</string>
|
||||||
<string name="settings_onepod_mode_description">Il n’est pas nécessaire de porter deux AirPods, en porter un seul suffit à déclencher les réactions.</string>
|
<string name="settings_onepod_mode_description">Il n’est pas nécessaire de porter deux AirPods, en porter un seul suffit à déclencher les réactions.</string>
|
||||||
<string name="permission_system_alert_window_label">Fenêtre d’alerte système</string>
|
<string name="permission_system_alert_window_label">Fenêtre d’alerte système</string>
|
||||||
@@ -127,4 +132,6 @@
|
|||||||
<string name="settings_blescanner_unfiltered_label">Données BÉB non filtrées</string>
|
<string name="settings_blescanner_unfiltered_label">Données BÉB non filtrées</string>
|
||||||
<string name="settings_blescanner_unfiltered_description">Supprimer tous les filtres de l’analyseur BÉB pour afficher toutes les données BÉB diffusées. Utile pour prendre en charge les nouveaux types de casques-micros.</string>
|
<string name="settings_blescanner_unfiltered_description">Supprimer tous les filtres de l’analyseur BÉB pour afficher toutes les données BÉB diffusées. Utile pour prendre en charge les nouveaux types de casques-micros.</string>
|
||||||
<string name="permission_required_title">L’autorisation suivante est nécessaire :</string>
|
<string name="permission_required_title">L’autorisation suivante est nécessaire :</string>
|
||||||
|
<string name="settings_compatibility_mode_label">Mode de compatibilité</string>
|
||||||
|
<string name="settings_compatibility_mode_description">Désactiver les optimisations pour améliorer la compatibilité. À essayer si vous ne voyez aucune donnée.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
<string name="settings_monitor_mode_label">Mode monitor</string>
|
<string name="settings_monitor_mode_label">Mode monitor</string>
|
||||||
<string name="settings_monitor_mode_description">Dalam keadaan apa aplikasi ini memonitor data Bluetooth.</string>
|
<string name="settings_monitor_mode_description">Dalam keadaan apa aplikasi ini memonitor data Bluetooth.</string>
|
||||||
<string name="settings_scanner_mode_label">Modus memindai</string>
|
<string name="settings_scanner_mode_label">Modus memindai</string>
|
||||||
|
<string name="settings_scanner_mode_description">Haruskah pemindai data Bluetooth Hemat Energi memprioritaskan kinerja atau menghemat energi?</string>
|
||||||
<string name="settings_monitor_mode_manual_label">Saat aplikasi terbuka</string>
|
<string name="settings_monitor_mode_manual_label">Saat aplikasi terbuka</string>
|
||||||
<string name="settings_monitor_mode_automatic_label">Saat perangkat terhubung</string>
|
<string name="settings_monitor_mode_automatic_label">Saat perangkat terhubung</string>
|
||||||
<string name="settings_monitor_mode_always_label">Selalu</string>
|
<string name="settings_monitor_mode_always_label">Selalu</string>
|
||||||
@@ -88,10 +89,13 @@
|
|||||||
<string name="settings_scanner_mode_balanced_label">Seimbang</string>
|
<string name="settings_scanner_mode_balanced_label">Seimbang</string>
|
||||||
<string name="settings_scanner_mode_lowlatency_label">Latemsi rendah</string>
|
<string name="settings_scanner_mode_lowlatency_label">Latemsi rendah</string>
|
||||||
<string name="settings_autopause_label">Jeda otomatis</string>
|
<string name="settings_autopause_label">Jeda otomatis</string>
|
||||||
|
<string name="settings_autopause_description">Jeda audio saat melepaskan perangkat dari telinga anda.</string>
|
||||||
<string name="settings_showall_label">Tampilkan semua perangkat</string>
|
<string name="settings_showall_label">Tampilkan semua perangkat</string>
|
||||||
<string name="settings_showall_description">Tampilkan perangkat orang lain yang ada di dekat anda.</string>
|
<string name="settings_showall_description">Tampilkan perangkat orang lain yang ada di dekat anda.</string>
|
||||||
<string name="settings_autopplay_label">Putar otomatis</string>
|
<string name="settings_autopplay_label">Putar otomatis</string>
|
||||||
|
<string name="settings_autoplay_description">Mulai pemutaran audio saat perangkat dipakai.</string>
|
||||||
<string name="settings_fake_data_label">Data palsu</string>
|
<string name="settings_fake_data_label">Data palsu</string>
|
||||||
|
<string name="settings_fake_data_description">Tampilkan data palsu, yaitu, mensimulasikan perangkat yang tidak ada.</string>
|
||||||
<string name="settings_debug_label">Pengaturan debug</string>
|
<string name="settings_debug_label">Pengaturan debug</string>
|
||||||
<string name="settings_debug_description">Pengaturan tambahan untuk membantu memecahkan masalah dengan aplikasi.</string>
|
<string name="settings_debug_description">Pengaturan tambahan untuk membantu memecahkan masalah dengan aplikasi.</string>
|
||||||
<string name="settings_signal_minimum_label">Kualitas sinyal minimum</string>
|
<string name="settings_signal_minimum_label">Kualitas sinyal minimum</string>
|
||||||
@@ -118,6 +122,7 @@
|
|||||||
<string name="overview_bluetooth_disabled_label">Bluetooth dinonaktifkan</string>
|
<string name="overview_bluetooth_disabled_label">Bluetooth dinonaktifkan</string>
|
||||||
<string name="overview_bluetooth_disabled_description">Bluetooth dinonaktifkan, aktifkan terlebih dahulu ;)</string>
|
<string name="overview_bluetooth_disabled_description">Bluetooth dinonaktifkan, aktifkan terlebih dahulu ;)</string>
|
||||||
<string name="help_translate_label">Terjemahan</string>
|
<string name="help_translate_label">Terjemahan</string>
|
||||||
|
<string name="help_translate_description">Bantu terjemahkan applikasi ini ke bahasa favorit anda.</string>
|
||||||
<string name="settings_onepod_mode_label">Mode satu pod</string>
|
<string name="settings_onepod_mode_label">Mode satu pod</string>
|
||||||
<string name="settings_onepod_mode_description">Menggunakan kedua pod tidak diperlukan, memakai satu pod sudah cukup untuk memicu reaksi.</string>
|
<string name="settings_onepod_mode_description">Menggunakan kedua pod tidak diperlukan, memakai satu pod sudah cukup untuk memicu reaksi.</string>
|
||||||
<string name="permission_system_alert_window_label">Jendela Peringatan Sistem</string>
|
<string name="permission_system_alert_window_label">Jendela Peringatan Sistem</string>
|
||||||
@@ -127,4 +132,6 @@
|
|||||||
<string name="settings_blescanner_unfiltered_label">Data BLE yang tidak difilter</string>
|
<string name="settings_blescanner_unfiltered_label">Data BLE yang tidak difilter</string>
|
||||||
<string name="settings_blescanner_unfiltered_description">Hapus semua filter dari pemindai BLE untuk menampilkan semua data BLE yang disiarkan. Berguna untuk menambahkan dukungan pada jenis headphone baru.</string>
|
<string name="settings_blescanner_unfiltered_description">Hapus semua filter dari pemindai BLE untuk menampilkan semua data BLE yang disiarkan. Berguna untuk menambahkan dukungan pada jenis headphone baru.</string>
|
||||||
<string name="permission_required_title">Izin berikut diperlukan:</string>
|
<string name="permission_required_title">Izin berikut diperlukan:</string>
|
||||||
|
<string name="settings_compatibility_mode_label">Mode kompatibilitas</string>
|
||||||
|
<string name="settings_compatibility_mode_description">Nonaktifkan pengoptimalan untuk meningkatkan kompatibilitas. Coba ini jika anda tidak melihat data apa pun.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
<string name="settings_monitor_mode_label">Monitor modus</string>
|
<string name="settings_monitor_mode_label">Monitor modus</string>
|
||||||
<string name="settings_monitor_mode_description">Onder welke omstandigheden houdt deze app Bluetooth-gegevens bij.</string>
|
<string name="settings_monitor_mode_description">Onder welke omstandigheden houdt deze app Bluetooth-gegevens bij.</string>
|
||||||
<string name="settings_scanner_mode_label">Scannermodus</string>
|
<string name="settings_scanner_mode_label">Scannermodus</string>
|
||||||
|
<string name="settings_scanner_mode_description">Moet de Bluetooth Low Energy-gegevensscanner prioriteit geven aan prestaties of energie besparen?</string>
|
||||||
<string name="settings_monitor_mode_manual_label">Wanneer app is geopend</string>
|
<string name="settings_monitor_mode_manual_label">Wanneer app is geopend</string>
|
||||||
<string name="settings_monitor_mode_automatic_label">Wanneer het apparaat is aangesloten</string>
|
<string name="settings_monitor_mode_automatic_label">Wanneer het apparaat is aangesloten</string>
|
||||||
<string name="settings_monitor_mode_always_label">Altijd</string>
|
<string name="settings_monitor_mode_always_label">Altijd</string>
|
||||||
@@ -88,10 +89,13 @@
|
|||||||
<string name="settings_scanner_mode_balanced_label">Gebalanceerd</string>
|
<string name="settings_scanner_mode_balanced_label">Gebalanceerd</string>
|
||||||
<string name="settings_scanner_mode_lowlatency_label">Lage latentie</string>
|
<string name="settings_scanner_mode_lowlatency_label">Lage latentie</string>
|
||||||
<string name="settings_autopause_label">Auto pauze</string>
|
<string name="settings_autopause_label">Auto pauze</string>
|
||||||
|
<string name="settings_autopause_description">Pauzeer de muziek wanneer u het apparaat van uw oor haalt.</string>
|
||||||
<string name="settings_showall_label">Alle apparaten weergeven</string>
|
<string name="settings_showall_label">Alle apparaten weergeven</string>
|
||||||
<string name="settings_showall_description">Toon apparaten van andere mensen die bij u in de buurt zijn.</string>
|
<string name="settings_showall_description">Toon apparaten van andere mensen die bij u in de buurt zijn.</string>
|
||||||
<string name="settings_autopplay_label">Automatisch afspelen</string>
|
<string name="settings_autopplay_label">Automatisch afspelen</string>
|
||||||
|
<string name="settings_autoplay_description">Start audioweergave wanneer het apparaat wordt gedragen.</string>
|
||||||
<string name="settings_fake_data_label">Nep gegevens</string>
|
<string name="settings_fake_data_label">Nep gegevens</string>
|
||||||
|
<string name="settings_fake_data_description">Toon nepgegevens, d.w.z. simuleer apparaten die niet bestaan.</string>
|
||||||
<string name="settings_debug_label">Foutopsporingsinstellingen</string>
|
<string name="settings_debug_label">Foutopsporingsinstellingen</string>
|
||||||
<string name="settings_debug_description">Aanvullende instellingen voor het oplossen van problemen met de app.</string>
|
<string name="settings_debug_description">Aanvullende instellingen voor het oplossen van problemen met de app.</string>
|
||||||
<string name="settings_signal_minimum_label">Minimale signaalkwaliteit</string>
|
<string name="settings_signal_minimum_label">Minimale signaalkwaliteit</string>
|
||||||
@@ -118,6 +122,7 @@
|
|||||||
<string name="overview_bluetooth_disabled_label">Bluetooth is uitgeschakeld</string>
|
<string name="overview_bluetooth_disabled_label">Bluetooth is uitgeschakeld</string>
|
||||||
<string name="overview_bluetooth_disabled_description">Bluetooth is uitgeschakeld, activeer het ;)</string>
|
<string name="overview_bluetooth_disabled_description">Bluetooth is uitgeschakeld, activeer het ;)</string>
|
||||||
<string name="help_translate_label">Vertaling</string>
|
<string name="help_translate_label">Vertaling</string>
|
||||||
|
<string name="help_translate_description">Help deze app te vertalen in uw favoriete taal.</string>
|
||||||
<string name="settings_onepod_mode_label">Eén pod-modus</string>
|
<string name="settings_onepod_mode_label">Eén pod-modus</string>
|
||||||
<string name="settings_onepod_mode_description">Het dragen van beide pods is niet vereist, het dragen van een enkele pod is voldoende om reacties uit te lokken.</string>
|
<string name="settings_onepod_mode_description">Het dragen van beide pods is niet vereist, het dragen van een enkele pod is voldoende om reacties uit te lokken.</string>
|
||||||
<string name="permission_system_alert_window_label">Systeemwaarschuwingsvenster</string>
|
<string name="permission_system_alert_window_label">Systeemwaarschuwingsvenster</string>
|
||||||
@@ -127,4 +132,6 @@
|
|||||||
<string name="settings_blescanner_unfiltered_label">Ongefilterde BLE-gegevens</string>
|
<string name="settings_blescanner_unfiltered_label">Ongefilterde BLE-gegevens</string>
|
||||||
<string name="settings_blescanner_unfiltered_description">Verwijder eventuele filters van de BLE-scanner om alle uitgezonden BLE-gegevens weer te geven. Handig om ondersteuning toe te voegen voor nieuwe typen hoofdtelefoons.</string>
|
<string name="settings_blescanner_unfiltered_description">Verwijder eventuele filters van de BLE-scanner om alle uitgezonden BLE-gegevens weer te geven. Handig om ondersteuning toe te voegen voor nieuwe typen hoofdtelefoons.</string>
|
||||||
<string name="permission_required_title">De volgende toestemming is vereist:</string>
|
<string name="permission_required_title">De volgende toestemming is vereist:</string>
|
||||||
|
<string name="settings_compatibility_mode_label">Compatibiliteitsmodus</string>
|
||||||
|
<string name="settings_compatibility_mode_description">Schakel optimalisaties uit om de compatibiliteit te verbeteren. Probeer dit als u geen gegevens ziet.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<string name="permission_bluetooth_label">蓝牙</string>
|
<string name="permission_bluetooth_label">蓝牙</string>
|
||||||
<string name="permission_bluetooth_description">本应用需要“蓝牙”权限才能连接已配对的蓝牙设备</string>
|
<string name="permission_bluetooth_description">本应用需要“蓝牙”权限才能连接已配对的蓝牙设备</string>
|
||||||
<string name="permission_access_fine_location_label">访问精准位置</string>
|
<string name="permission_access_fine_location_label">访问精准位置</string>
|
||||||
<string name="permission_access_fine_location_description">CAPod 使用“精准定位”权限来接收低功耗蓝牙数据。您的耳机使用低功耗蓝牙技术来传播其状态,本应用「不会」通过蓝牙数据来给您定位。</string>
|
<string name="permission_access_fine_location_description">CAPod 使用“精准定位”权限来接收低功耗蓝牙数据。您的耳机使用低功耗蓝牙技术来广播其状态,本应用「不会」通过蓝牙数据测定您的位置。</string>
|
||||||
<string name="permission_background_location_label">在后台访问位置</string>
|
<string name="permission_background_location_label">在后台访问位置</string>
|
||||||
<string name="permission_background_location_description">CAPod 使用“在后台访问位置”权限以在本应用被关闭时继续提供如“显示弹窗”、“自动连接”等功能。此权限允许本应用在后台运行时继续接收蓝牙低功耗数据。本应用并不使用蓝牙数据来窥探您的位置。</string>
|
<string name="permission_background_location_description">CAPod 使用“在后台访问位置”权限以在本应用被关闭时继续提供如“显示弹窗”、“自动连接”等功能。此权限允许本应用在后台运行时继续接收蓝牙低功耗数据。本应用并不使用蓝牙数据来窥探您的位置。</string>
|
||||||
<string name="permission_ignore_battery_optimizations_label">禁用电池优化</string>
|
<string name="permission_ignore_battery_optimizations_label">禁用电池优化</string>
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
<string name="settings_debug_mode_label">调试模式</string>
|
<string name="settings_debug_mode_label">调试模式</string>
|
||||||
<string name="settings_debug_mode_description">显示额外信息以帮助调试故障。</string>
|
<string name="settings_debug_mode_description">显示额外信息以帮助调试故障。</string>
|
||||||
<string name="settings_monitor_mode_label">监控模式</string>
|
<string name="settings_monitor_mode_label">监控模式</string>
|
||||||
<string name="settings_monitor_mode_description">此模式下本应用将监视蓝牙数据。</string>
|
<string name="settings_monitor_mode_description">决定本应用在何时收听蓝牙数据。</string>
|
||||||
<string name="settings_scanner_mode_label">扫描模式</string>
|
<string name="settings_scanner_mode_label">扫描模式</string>
|
||||||
<string name="settings_scanner_mode_description">低功耗蓝牙数据扫描器应着重性能还是省电?</string>
|
<string name="settings_scanner_mode_description">低功耗蓝牙数据扫描器应着重性能还是省电?</string>
|
||||||
<string name="settings_monitor_mode_manual_label">应用打开时</string>
|
<string name="settings_monitor_mode_manual_label">应用打开时</string>
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
<string name="settings_popup_caseopen_label">显示弹窗</string>
|
<string name="settings_popup_caseopen_label">显示弹窗</string>
|
||||||
<string name="settings_popup_caseopen_description">设备充电仓打开时显示弹窗(实验性)</string>
|
<string name="settings_popup_caseopen_description">设备充电仓打开时显示弹窗(实验性)</string>
|
||||||
<string name="overview_bluetooth_disabled_label">蓝牙已禁用</string>
|
<string name="overview_bluetooth_disabled_label">蓝牙已禁用</string>
|
||||||
<string name="overview_bluetooth_disabled_description">蓝牙已关闭,请启用之 ;)</string>
|
<string name="overview_bluetooth_disabled_description">蓝牙已关闭,请开启 ;)</string>
|
||||||
<string name="help_translate_label">翻译</string>
|
<string name="help_translate_label">翻译</string>
|
||||||
<string name="help_translate_description">帮助翻译本应用</string>
|
<string name="help_translate_description">帮助翻译本应用</string>
|
||||||
<string name="settings_onepod_mode_label">单耳模式</string>
|
<string name="settings_onepod_mode_label">单耳模式</string>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
<string name="settings_support_installid_label">安裝 ID</string>
|
<string name="settings_support_installid_label">安裝 ID</string>
|
||||||
<string name="settings_support_installid_desc">自動傳送的錯誤報告是匿名的。如果開發人員需要找出你的錯誤報告將分享你的安裝 ID。</string>
|
<string name="settings_support_installid_desc">自動傳送的錯誤報告是匿名的。如果開發人員需要找出你的錯誤報告將分享你的安裝 ID。</string>
|
||||||
<string name="settings_support_label">支援</string>
|
<string name="settings_support_label">支援</string>
|
||||||
<string name="settings_support_description">如果你需要幫助。</string>
|
<string name="settings_support_description">如果你需要協助。</string>
|
||||||
<string name="issue_tracker_label">問題追蹤器</string>
|
<string name="issue_tracker_label">問題追蹤器</string>
|
||||||
<string name="issue_tracker_description">一個用於錯誤回報和功能需求的公用問題追蹤器 (僅英文)。</string>
|
<string name="issue_tracker_description">一個用於錯誤回報和功能需求的公用問題追蹤器 (僅英文)。</string>
|
||||||
<string name="discord_label">Discord</string>
|
<string name="discord_label">Discord</string>
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
<string name="settings_fake_data_label">假資料</string>
|
<string name="settings_fake_data_label">假資料</string>
|
||||||
<string name="settings_fake_data_description">顯示假資料,模擬不存在的裝置。</string>
|
<string name="settings_fake_data_description">顯示假資料,模擬不存在的裝置。</string>
|
||||||
<string name="settings_debug_label">偵錯設定</string>
|
<string name="settings_debug_label">偵錯設定</string>
|
||||||
<string name="settings_debug_description">一個能幫助這個應用程式故障檢修的附加設定。</string>
|
<string name="settings_debug_description">一個能協助這個應用程式故障檢修的附加設定。</string>
|
||||||
<string name="settings_signal_minimum_label">最低訊號質量</string>
|
<string name="settings_signal_minimum_label">最低訊號質量</string>
|
||||||
<string name="settings_signal_minimum_description">可以被認為是你的裝置的最低訊號質量。</string>
|
<string name="settings_signal_minimum_description">可以被認為是你的裝置的最低訊號質量。</string>
|
||||||
<string name="settings_autoconnect_label">自動連線</string>
|
<string name="settings_autoconnect_label">自動連線</string>
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
<string name="settings_maindevice_address_description">你的已配對裝置的位址。這個應用程式用該位址來確定何時連線到你的手機。</string>
|
<string name="settings_maindevice_address_description">你的已配對裝置的位址。這個應用程式用該位址來確定何時連線到你的手機。</string>
|
||||||
<string name="settings_maindevice_address_none">無</string>
|
<string name="settings_maindevice_address_none">無</string>
|
||||||
<string name="settings_maindevice_model_label">你的裝置型號</string>
|
<string name="settings_maindevice_model_label">你的裝置型號</string>
|
||||||
<string name="settings_maindevice_model_description">你的主要裝置型號。這能幫助這個應用程式在未連線到你的手機時辨識你的裝置。</string>
|
<string name="settings_maindevice_model_description">你的主要裝置型號。這能協助這個應用程式在未連線到你的手機時辨識你的裝置。</string>
|
||||||
<string name="settings_reaction_autoconnect_whenseen_label">看到時</string>
|
<string name="settings_reaction_autoconnect_whenseen_label">看到時</string>
|
||||||
<string name="settings_reaction_autoconnect_caseopen_label">充電盒開啟</string>
|
<string name="settings_reaction_autoconnect_caseopen_label">充電盒開啟</string>
|
||||||
<string name="settings_reaction_autoconnect_inear_label">在耳中</string>
|
<string name="settings_reaction_autoconnect_inear_label">在耳中</string>
|
||||||
|
|||||||
@@ -144,4 +144,6 @@
|
|||||||
<string name="permission_required_title">The following permission is required:</string>
|
<string name="permission_required_title">The following permission is required:</string>
|
||||||
<string name="settings_compatibility_mode_label">Compatibility mode</string>
|
<string name="settings_compatibility_mode_label">Compatibility mode</string>
|
||||||
<string name="settings_compatibility_mode_description">Disable optimizations to improve compatibility. Try this if you are not seeing any data.</string>
|
<string name="settings_compatibility_mode_description">Disable optimizations to improve compatibility. Try this if you are not seeing any data.</string>
|
||||||
|
<string name="translators_thanks_title">Translators</string>
|
||||||
|
<string name="translators_thanks_description">darken</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -4,6 +4,15 @@
|
|||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:title="@string/general_thank_you_label"
|
android:title="@string/general_thank_you_label"
|
||||||
app:iconSpaceReserved="false">
|
app:iconSpaceReserved="false">
|
||||||
|
<Preference
|
||||||
|
android:key="thanks.translators"
|
||||||
|
android:summary="@string/translators_thanks_description"
|
||||||
|
android:title="@string/translators_thanks_title"
|
||||||
|
app:iconSpaceReserved="false">
|
||||||
|
<intent
|
||||||
|
android:action="android.intent.action.VIEW"
|
||||||
|
android:data="https://crowdin.com/project/capod/activity-stream" />
|
||||||
|
</Preference>
|
||||||
<Preference
|
<Preference
|
||||||
android:key="thanks.maxpatchs"
|
android:key="thanks.maxpatchs"
|
||||||
android:summary="Thanks for the lovely icons."
|
android:summary="Thanks for the lovely icons."
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package eu.darken.capod.pods.core.apple
|
|||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen1
|
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen1
|
||||||
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro
|
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro
|
||||||
|
import eu.darken.capod.pods.core.apple.misc.UnknownAppleDevice
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import io.kotest.matchers.types.instanceOf
|
import io.kotest.matchers.types.instanceOf
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
@@ -12,14 +13,14 @@ class AirPodsFactoryTest : BaseAirPodsTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `create AirPodsGen1`() = runBlockingTest {
|
fun `create AirPodsGen1`() = runBlockingTest {
|
||||||
create<DualApplePods>("07 19 01 02 20 55 AA 56 31 00 00 6F E4 DF 10 AF 10 60 81 03 3B 76 D9 C7 11 22 88") {
|
create<DualAirPods>("07 19 01 02 20 55 AA 56 31 00 00 6F E4 DF 10 AF 10 60 81 03 3B 76 D9 C7 11 22 88") {
|
||||||
this shouldBe instanceOf<AirPodsGen1>()
|
this shouldBe instanceOf<AirPodsGen1>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `create AirPodsPro`() = runBlockingTest {
|
fun `create AirPodsPro`() = runBlockingTest {
|
||||||
create<DualApplePods>("07 19 01 0E 20 2B 99 8F 01 00 >09< 10 30 EE F3 41 B5 D8 9F A3 B0 B4 17 9F 85 97 5F") {
|
create<DualAirPods>("07 19 01 0E 20 2B 99 8F 01 00 >09< 10 30 EE F3 41 B5 D8 9F A3 B0 B4 17 9F 85 97 5F") {
|
||||||
this shouldBe instanceOf<AirPodsPro>()
|
this shouldBe instanceOf<AirPodsPro>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class BasicSingleApplePodsTest : BaseAirPodsTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test mapping`() = runBlockingTest {
|
fun `test mapping`() = runBlockingTest {
|
||||||
create<BasicSingleApplePods>("07 19 01 05 20 00 F5 0F 01 01 00 6D CE C0 04 22 0A 85 31 2D 82 6B 42 80 01 20 1A") {
|
create<SingleApplePods>("07 19 01 05 20 00 F5 0F 01 01 00 6D CE C0 04 22 0A 85 31 2D 82 6B 42 80 01 20 1A") {
|
||||||
rawPrefix shouldBe 0x01.toUByte()
|
rawPrefix shouldBe 0x01.toUByte()
|
||||||
rawDeviceModel shouldBe 0x0520.toUShort()
|
rawDeviceModel shouldBe 0x0520.toUShort()
|
||||||
rawStatus shouldBe 0x00.toUByte()
|
rawStatus shouldBe 0x00.toUByte()
|
||||||
@@ -23,7 +23,7 @@ class BasicSingleApplePodsTest : BaseAirPodsTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test battery headset percent`() = runBlockingTest {
|
fun `test battery headset percent`() = runBlockingTest {
|
||||||
create<BasicSingleApplePods>("07 19 01 05 20 00 F5 0F 01 01 00 6D CE C0 04 22 0A 85 31 2D 82 6B 42 80 01 20 1A") {
|
create<SingleApplePods>("07 19 01 05 20 00 F5 0F 01 01 00 6D CE C0 04 22 0A 85 31 2D 82 6B 42 80 01 20 1A") {
|
||||||
batteryHeadsetPercent shouldBe 0.5f
|
batteryHeadsetPercent shouldBe 0.5f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class DualApplePodsTest : BaseAirPodsTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test bit mapping`() = runBlockingTest {
|
fun `test bit mapping`() = runBlockingTest {
|
||||||
create<DualApplePods>("07 19 01 0E 20 54 AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
create<DualAirPods>("07 19 01 0E 20 54 AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
||||||
|
|
||||||
rawPrefix shouldBe 0x01.toUByte()
|
rawPrefix shouldBe 0x01.toUByte()
|
||||||
rawDeviceModel shouldBe 0x0e20.toUShort()
|
rawDeviceModel shouldBe 0x0e20.toUShort()
|
||||||
@@ -24,13 +24,13 @@ class DualApplePodsTest : BaseAirPodsTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test AirPodDevice - active microphone`() = runBlockingTest {
|
fun `test AirPodDevice - active microphone`() = runBlockingTest {
|
||||||
create<DualApplePods>("07 19 01 0E 20 >2B< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
create<DualAirPods>("07 19 01 0E 20 >2B< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
||||||
// 00101011
|
// 00101011
|
||||||
// --^-----
|
// --^-----
|
||||||
isLeftPodMicrophone shouldBe true
|
isLeftPodMicrophone shouldBe true
|
||||||
isRightPodMicrophone shouldBe false
|
isRightPodMicrophone shouldBe false
|
||||||
}
|
}
|
||||||
create<DualApplePods>("07 19 01 0E 20 >0B< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
create<DualAirPods>("07 19 01 0E 20 >0B< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
||||||
// 00001011
|
// 00001011
|
||||||
// --^-----
|
// --^-----
|
||||||
isLeftPodMicrophone shouldBe false
|
isLeftPodMicrophone shouldBe false
|
||||||
@@ -41,24 +41,24 @@ class DualApplePodsTest : BaseAirPodsTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test AirPodDevice - left pod ear status`() = runBlockingTest {
|
fun `test AirPodDevice - left pod ear status`() = runBlockingTest {
|
||||||
// Left Pod primary
|
// Left Pod primary
|
||||||
create<DualApplePods>("07 19 01 0E 20 >22< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
create<DualAirPods>("07 19 01 0E 20 >22< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
||||||
// 00100010
|
// 00100010
|
||||||
// 765432¹0
|
// 765432¹0
|
||||||
isLeftPodInEar shouldBe true
|
isLeftPodInEar shouldBe true
|
||||||
}
|
}
|
||||||
create<DualApplePods>("07 19 01 0E 20 >20< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
create<DualAirPods>("07 19 01 0E 20 >20< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
||||||
// 00100000
|
// 00100000
|
||||||
// 765432¹0
|
// 765432¹0
|
||||||
isLeftPodInEar shouldBe false
|
isLeftPodInEar shouldBe false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right Pod is primary
|
// Right Pod is primary
|
||||||
create<DualApplePods>("07 19 01 0E 20 >09< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
create<DualAirPods>("07 19 01 0E 20 >09< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
||||||
// 00001001
|
// 00001001
|
||||||
// 7654³210
|
// 7654³210
|
||||||
isLeftPodInEar shouldBe true
|
isLeftPodInEar shouldBe true
|
||||||
}
|
}
|
||||||
create<DualApplePods>("07 19 01 0E 20 >20< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
create<DualAirPods>("07 19 01 0E 20 >20< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
||||||
// 00000001
|
// 00000001
|
||||||
// 7654³210
|
// 7654³210
|
||||||
isLeftPodInEar shouldBe false
|
isLeftPodInEar shouldBe false
|
||||||
@@ -68,24 +68,24 @@ class DualApplePodsTest : BaseAirPodsTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test AirPodDevice - right pod ear status`() = runBlockingTest {
|
fun `test AirPodDevice - right pod ear status`() = runBlockingTest {
|
||||||
// Left Pod primary
|
// Left Pod primary
|
||||||
create<DualApplePods>("07 19 01 0E 20 >29< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
create<DualAirPods>("07 19 01 0E 20 >29< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
||||||
// 00101001
|
// 00101001
|
||||||
// 7654³210
|
// 7654³210
|
||||||
isRightPodInEar shouldBe true
|
isRightPodInEar shouldBe true
|
||||||
}
|
}
|
||||||
create<DualApplePods>("07 19 01 0E 20 >21< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
create<DualAirPods>("07 19 01 0E 20 >21< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
||||||
// 00100001
|
// 00100001
|
||||||
// 7654³210
|
// 7654³210
|
||||||
isRightPodInEar shouldBe false
|
isRightPodInEar shouldBe false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right Pod is primary
|
// Right Pod is primary
|
||||||
create<DualApplePods>("07 19 01 0E 20 >03< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
create<DualAirPods>("07 19 01 0E 20 >03< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
||||||
// 00000011
|
// 00000011
|
||||||
// 765432¹0
|
// 765432¹0
|
||||||
isRightPodInEar shouldBe true
|
isRightPodInEar shouldBe true
|
||||||
}
|
}
|
||||||
create<DualApplePods>("07 19 01 0E 20 >01< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
create<DualAirPods>("07 19 01 0E 20 >01< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
||||||
// 00000001
|
// 00000001
|
||||||
// 765432¹0
|
// 765432¹0
|
||||||
isRightPodInEar shouldBe false
|
isRightPodInEar shouldBe false
|
||||||
@@ -95,13 +95,13 @@ class DualApplePodsTest : BaseAirPodsTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test AirPodDevice - battery status`() = runBlockingTest {
|
fun `test AirPodDevice - battery status`() = runBlockingTest {
|
||||||
// Right Pod is primary
|
// Right Pod is primary
|
||||||
create<DualApplePods>("07 19 01 0E 20 0B >98< 94 52 00 05 09 73 3C 3D F9 2C 3E B3 DD 76 02 DD 4E 16 FD FB") {
|
create<DualAirPods>("07 19 01 0E 20 0B >98< 94 52 00 05 09 73 3C 3D F9 2C 3E B3 DD 76 02 DD 4E 16 FD FB") {
|
||||||
// 88 10001000
|
// 88 10001000
|
||||||
batteryLeftPodPercent shouldBe 0.9f
|
batteryLeftPodPercent shouldBe 0.9f
|
||||||
batteryRightPodPercent shouldBe 0.8f
|
batteryRightPodPercent shouldBe 0.8f
|
||||||
}
|
}
|
||||||
// Left Pod primary
|
// Left Pod primary
|
||||||
create<DualApplePods>("07 19 01 0E 20 2B >89< 94 52 00 05 09 73 3C 3D F9 2C 3E B3 DD 76 02 DD 4E 16 FD FB") {
|
create<DualAirPods>("07 19 01 0E 20 2B >89< 94 52 00 05 09 73 3C 3D F9 2C 3E B3 DD 76 02 DD 4E 16 FD FB") {
|
||||||
// F8 11111000
|
// F8 11111000
|
||||||
batteryLeftPodPercent shouldBe 0.9f
|
batteryLeftPodPercent shouldBe 0.9f
|
||||||
batteryRightPodPercent shouldBe 0.8f
|
batteryRightPodPercent shouldBe 0.8f
|
||||||
@@ -114,13 +114,13 @@ class DualApplePodsTest : BaseAirPodsTest() {
|
|||||||
* Right pod is charging
|
* Right pod is charging
|
||||||
*/
|
*/
|
||||||
// This is the left
|
// This is the left
|
||||||
create<DualApplePods>("07 19 01 0E 20 51 89 >94< 52 00 00 F4 89 82 6D 3E 27 7F 26 62 57 D0 E2 A6 49 E9 35") {
|
create<DualAirPods>("07 19 01 0E 20 51 89 >94< 52 00 00 F4 89 82 6D 3E 27 7F 26 62 57 D0 E2 A6 49 E9 35") {
|
||||||
// 1001 0100
|
// 1001 0100
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
isRightPodCharging shouldBe true
|
isRightPodCharging shouldBe true
|
||||||
}
|
}
|
||||||
// This is the right
|
// This is the right
|
||||||
create<DualApplePods>("07 19 01 0E 20 31 98 >A4< 01 00 00 31 B9 A0 C4 80 CD D1 CF B9 3A 9A 6D 48 31 08 EB") {
|
create<DualAirPods>("07 19 01 0E 20 31 98 >A4< 01 00 00 31 B9 A0 C4 80 CD D1 CF B9 3A 9A 6D 48 31 08 EB") {
|
||||||
// 1010 0100
|
// 1010 0100
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
isRightPodCharging shouldBe true
|
isRightPodCharging shouldBe true
|
||||||
@@ -130,26 +130,26 @@ class DualApplePodsTest : BaseAirPodsTest() {
|
|||||||
* Left pod is charging
|
* Left pod is charging
|
||||||
*/
|
*/
|
||||||
// This is the left
|
// This is the left
|
||||||
create<DualApplePods>("07 19 01 0E 20 71 98 >94< 52 00 05 A5 37 31 B2 BD 42 68 0C 64 FD 00 99 4A E5 3E F4") {
|
create<DualAirPods>("07 19 01 0E 20 71 98 >94< 52 00 05 A5 37 31 B2 BD 42 68 0C 64 FD 00 99 4A E5 3E F4") {
|
||||||
// 1001 0100
|
// 1001 0100
|
||||||
isLeftPodCharging shouldBe true
|
isLeftPodCharging shouldBe true
|
||||||
isRightPodCharging shouldBe false
|
isRightPodCharging shouldBe false
|
||||||
}
|
}
|
||||||
// This is the right
|
// This is the right
|
||||||
create<DualApplePods>("07 19 01 0E 20 11 89 >A4< 04 00 04 BA 79 1B C0 65 69 C6 9F 19 6E 37 7D 6D 86 8D D9") {
|
create<DualAirPods>("07 19 01 0E 20 11 89 >A4< 04 00 04 BA 79 1B C0 65 69 C6 9F 19 6E 37 7D 6D 86 8D D9") {
|
||||||
// 1010 0100
|
// 1010 0100
|
||||||
isLeftPodCharging shouldBe true
|
isLeftPodCharging shouldBe true
|
||||||
isRightPodCharging shouldBe false
|
isRightPodCharging shouldBe false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Both charging
|
// Both charging
|
||||||
create<DualApplePods>("07 19 01 0E 20 55 88 >B4< 59 00 05 4B FC DF 68 28 A5 45 52 65 9C FE 51 86 3A B5 DB") {
|
create<DualAirPods>("07 19 01 0E 20 55 88 >B4< 59 00 05 4B FC DF 68 28 A5 45 52 65 9C FE 51 86 3A B5 DB") {
|
||||||
// 1011 0100
|
// 1011 0100
|
||||||
isLeftPodCharging shouldBe true
|
isLeftPodCharging shouldBe true
|
||||||
isRightPodCharging shouldBe true
|
isRightPodCharging shouldBe true
|
||||||
}
|
}
|
||||||
// Both not charging
|
// Both not charging
|
||||||
create<DualApplePods>("07 19 01 0E 20 00 F8 >8F< 03 00 05 4C 0F A0 C4 05 24 DD EB AF 92 99 FD 54 B1 06 48") {
|
create<DualAirPods>("07 19 01 0E 20 00 F8 >8F< 03 00 05 4C 0F A0 C4 05 24 DD EB AF 92 99 FD 54 B1 06 48") {
|
||||||
// 1000 1111
|
// 1000 1111
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
isRightPodCharging shouldBe false
|
isRightPodCharging shouldBe false
|
||||||
@@ -158,11 +158,11 @@ class DualApplePodsTest : BaseAirPodsTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test AirPodDevice - case charging`() = runBlockingTest {
|
fun `test AirPodDevice - case charging`() = runBlockingTest {
|
||||||
create<DualApplePods>("07 19 01 0E 20 75 99 >B4< 31 00 05 77 C8 BA 0C 4E 1F BE AD 70 C5 40 71 D2 E9 17 A2") {
|
create<DualAirPods>("07 19 01 0E 20 75 99 >B4< 31 00 05 77 C8 BA 0C 4E 1F BE AD 70 C5 40 71 D2 E9 17 A2") {
|
||||||
// 0011 0011
|
// 0011 0011
|
||||||
isCaseCharging shouldBe false
|
isCaseCharging shouldBe false
|
||||||
}
|
}
|
||||||
create<DualApplePods>("07 19 01 0E 20 75 A9 >F4< 51 00 05 A0 37 92 35 49 79 CC DC 27 94 8E FB 72 12 94 52") {
|
create<DualAirPods>("07 19 01 0E 20 75 A9 >F4< 51 00 05 A0 37 92 35 49 79 CC DC 27 94 8E FB 72 12 94 52") {
|
||||||
// 0101 0011
|
// 0101 0011
|
||||||
isCaseCharging shouldBe true
|
isCaseCharging shouldBe true
|
||||||
}
|
}
|
||||||
@@ -171,75 +171,75 @@ class DualApplePodsTest : BaseAirPodsTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test AirPodDevice - case lid test`() = runBlockingTest {
|
fun `test AirPodDevice - case lid test`() = runBlockingTest {
|
||||||
// Lid open
|
// Lid open
|
||||||
create<DualApplePods>("07 19 01 0E 20 55 AA B4 >31< 00 00 A1 D0 BD 82 D3 52 86 CA FC 11 62 DC 42 C6 92 8E") {
|
create<DualAirPods>("07 19 01 0E 20 55 AA B4 >31< 00 00 A1 D0 BD 82 D3 52 86 CA FC 11 62 DC 42 C6 92 8E") {
|
||||||
// 31 0011 0001
|
// 31 0011 0001
|
||||||
caseLidState shouldBe DualApplePods.LidState.OPEN
|
caseLidState shouldBe DualAirPods.LidState.OPEN
|
||||||
}
|
}
|
||||||
// Lid open, left pod in case
|
// Lid open, left pod in case
|
||||||
create<DualApplePods>("07 19 01 0E 20 51 9A 93 >31< 00 00 95 D0 A5 D7 E3 F4 F1 38 38 99 61 3B 57 95 37 B7") {
|
create<DualAirPods>("07 19 01 0E 20 51 9A 93 >31< 00 00 95 D0 A5 D7 E3 F4 F1 38 38 99 61 3B 57 95 37 B7") {
|
||||||
// 31 0011 0001
|
// 31 0011 0001
|
||||||
caseLidState shouldBe DualApplePods.LidState.OPEN
|
caseLidState shouldBe DualAirPods.LidState.OPEN
|
||||||
}
|
}
|
||||||
// Lid open, left pod in case
|
// Lid open, left pod in case
|
||||||
create<DualApplePods>("07 19 01 0E 20 71 A9 92 31 00 00 DB 48 7F 32 8C CE 80 6F D9 27 98 D6 76 45 9D 62") {
|
create<DualAirPods>("07 19 01 0E 20 71 A9 92 31 00 00 DB 48 7F 32 8C CE 80 6F D9 27 98 D6 76 45 9D 62") {
|
||||||
// 31 0011 0001
|
// 31 0011 0001
|
||||||
caseLidState shouldBe DualApplePods.LidState.OPEN
|
caseLidState shouldBe DualAirPods.LidState.OPEN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Lid just closed
|
// Lid just closed
|
||||||
create<DualApplePods>("07 19 01 0E 20 55 AA B4 >39< 00 00 08 A6 DB 99 E0 5E 14 85 E5 C2 0B 68 D7 FF C3 A1") {
|
create<DualAirPods>("07 19 01 0E 20 55 AA B4 >39< 00 00 08 A6 DB 99 E0 5E 14 85 E5 C2 0B 68 D7 FF C3 A1") {
|
||||||
// 39 0011 1001
|
// 39 0011 1001
|
||||||
caseLidState shouldBe DualApplePods.LidState.CLOSED
|
caseLidState shouldBe DualAirPods.LidState.CLOSED
|
||||||
}
|
}
|
||||||
// Lid closed
|
// Lid closed
|
||||||
create<DualApplePods>("07 19 01 0E 20 55 AA B4 38 00 00 F3 F7 08 3B 98 09 C0 DD E4 BD BD 84 55 56 8B 81") {
|
create<DualAirPods>("07 19 01 0E 20 55 AA B4 38 00 00 F3 F7 08 3B 98 09 C0 DD E4 BD BD 84 55 56 8B 81") {
|
||||||
// 38 0011 1000
|
// 38 0011 1000
|
||||||
caseLidState shouldBe DualApplePods.LidState.CLOSED
|
caseLidState shouldBe DualAirPods.LidState.CLOSED
|
||||||
}
|
}
|
||||||
// Lid closed, right pod in case
|
// Lid closed, right pod in case
|
||||||
create<DualApplePods>("07 19 01 0E 20 51 9A 93 >38< 00 00 3A D8 85 76 B0 91 48 31 DA FF 6C 4A 2B C2 67 F4") {
|
create<DualAirPods>("07 19 01 0E 20 51 9A 93 >38< 00 00 3A D8 85 76 B0 91 48 31 DA FF 6C 4A 2B C2 67 F4") {
|
||||||
// 38 0011 1000
|
// 38 0011 1000
|
||||||
caseLidState shouldBe DualApplePods.LidState.CLOSED
|
caseLidState shouldBe DualAirPods.LidState.CLOSED
|
||||||
}
|
}
|
||||||
// Lid closed, left pod in case
|
// Lid closed, left pod in case
|
||||||
create<DualApplePods>("07 19 01 0E 20 71 A9 92 38 00 00 44 91 C4 8B 85 98 DD 55 4E 6A CA BC B5 CA 8D 37") {
|
create<DualAirPods>("07 19 01 0E 20 71 A9 92 38 00 00 44 91 C4 8B 85 98 DD 55 4E 6A CA BC B5 CA 8D 37") {
|
||||||
// 38 0011 1000
|
// 38 0011 1000
|
||||||
caseLidState shouldBe DualApplePods.LidState.CLOSED
|
caseLidState shouldBe DualAirPods.LidState.CLOSED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test AirPodDevice - connection state`() = runBlockingTest {
|
fun `test AirPodDevice - connection state`() = runBlockingTest {
|
||||||
// Disconnected
|
// Disconnected
|
||||||
create<DualApplePods>("07 19 01 0E 20 2B AA 8F 01 00 >00< 62 D4 BB F1 A7 F8 64 98 D2 C8 BD 7B 3A EF 2E 15") {
|
create<DualAirPods>("07 19 01 0E 20 2B AA 8F 01 00 >00< 62 D4 BB F1 A7 F8 64 98 D2 C8 BD 7B 3A EF 2E 15") {
|
||||||
// 31 0011 0001
|
// 31 0011 0001
|
||||||
connectionState shouldBe DualApplePods.ConnectionState.DISCONNECTED
|
state shouldBe DualAirPods.ConnectionState.DISCONNECTED
|
||||||
}
|
}
|
||||||
// Connected idle
|
// Connected idle
|
||||||
create<DualApplePods>("07 19 01 0E 20 2B AA 8F 01 00 >04< 1D 69 69 9C C2 51 F3 1F BF 6E 45 DA 90 4A A3 E3") {
|
create<DualAirPods>("07 19 01 0E 20 2B AA 8F 01 00 >04< 1D 69 69 9C C2 51 F3 1F BF 6E 45 DA 90 4A A3 E3") {
|
||||||
// 39 0011 1001
|
// 39 0011 1001
|
||||||
connectionState shouldBe DualApplePods.ConnectionState.IDLE
|
state shouldBe DualAirPods.ConnectionState.IDLE
|
||||||
}
|
}
|
||||||
// Connected and playing music
|
// Connected and playing music
|
||||||
create<DualApplePods>("07 19 01 0E 20 2B A9 8F 01 00 >05< 14 F7 CB 49 9F D3 B3 22 77 D2 22 F1 74 8C AC A6") {
|
create<DualAirPods>("07 19 01 0E 20 2B A9 8F 01 00 >05< 14 F7 CB 49 9F D3 B3 22 77 D2 22 F1 74 8C AC A6") {
|
||||||
// 38 0011 1000
|
// 38 0011 1000
|
||||||
connectionState shouldBe DualApplePods.ConnectionState.MUSIC
|
state shouldBe DualAirPods.ConnectionState.MUSIC
|
||||||
}
|
}
|
||||||
// Connected and call active
|
// Connected and call active
|
||||||
create<DualApplePods>("07 19 01 0E 20 2B 99 8F 01 00 >06< 0F 4B 43 25 E0 4A 73 63 14 22 C2 3C 89 13 BD 97") {
|
create<DualAirPods>("07 19 01 0E 20 2B 99 8F 01 00 >06< 0F 4B 43 25 E0 4A 73 63 14 22 C2 3C 89 13 BD 97") {
|
||||||
// 38 0011 1000
|
// 38 0011 1000
|
||||||
connectionState shouldBe DualApplePods.ConnectionState.CALL
|
state shouldBe DualAirPods.ConnectionState.CALL
|
||||||
}
|
}
|
||||||
// Connected and call active
|
// Connected and call active
|
||||||
create<DualApplePods>("07 19 01 0E 20 2B 99 8F 01 00 >07< E7 DF 76 44 85 B5 30 F4 95 14 02 DC A1 A4 8A 09") {
|
create<DualAirPods>("07 19 01 0E 20 2B 99 8F 01 00 >07< E7 DF 76 44 85 B5 30 F4 95 14 02 DC A1 A4 8A 09") {
|
||||||
// 38 0011 1000
|
// 38 0011 1000
|
||||||
connectionState shouldBe DualApplePods.ConnectionState.RINGING
|
state shouldBe DualAirPods.ConnectionState.RINGING
|
||||||
}
|
}
|
||||||
// Switching?
|
// Switching?
|
||||||
create<DualApplePods>("07 19 01 0E 20 2B 99 8F 01 00 >09< 10 30 EE F3 41 B5 D8 9F A3 B0 B4 17 9F 85 97 5F") {
|
create<DualAirPods>("07 19 01 0E 20 2B 99 8F 01 00 >09< 10 30 EE F3 41 B5 D8 9F A3 B0 B4 17 9F 85 97 5F") {
|
||||||
// 38 0011 1000
|
// 38 0011 1000
|
||||||
connectionState shouldBe DualApplePods.ConnectionState.HANGING_UP
|
state shouldBe DualAirPods.ConnectionState.HANGING_UP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package eu.darken.capod.pods.core.apple.airpods
|
package eu.darken.capod.pods.core.apple.airpods
|
||||||
|
|
||||||
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
|
import eu.darken.capod.pods.core.apple.HasAppleColor
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -33,11 +34,11 @@ class AirPodsGen1Test : BaseAirPodsTest() {
|
|||||||
isRightPodInEar shouldBe false
|
isRightPodInEar shouldBe false
|
||||||
batteryCasePercent shouldBe 0.6f
|
batteryCasePercent shouldBe 0.6f
|
||||||
|
|
||||||
caseLidState shouldBe DualApplePods.LidState.OPEN
|
caseLidState shouldBe DualAirPods.LidState.OPEN
|
||||||
|
|
||||||
connectionState shouldBe DualApplePods.ConnectionState.DISCONNECTED
|
state shouldBe DualAirPods.ConnectionState.DISCONNECTED
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package eu.darken.capod.pods.core.apple.airpods
|
package eu.darken.capod.pods.core.apple.airpods
|
||||||
|
|
||||||
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
|
import eu.darken.capod.pods.core.apple.HasAppleColor
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -32,11 +33,11 @@ class AirPodsGen2Test : BaseAirPodsTest() {
|
|||||||
isRightPodInEar shouldBe true
|
isRightPodInEar shouldBe true
|
||||||
batteryCasePercent shouldBe null
|
batteryCasePercent shouldBe null
|
||||||
|
|
||||||
caseLidState shouldBe DualApplePods.LidState.NOT_IN_CASE
|
caseLidState shouldBe DualAirPods.LidState.NOT_IN_CASE
|
||||||
|
|
||||||
connectionState shouldBe DualApplePods.ConnectionState.MUSIC
|
state shouldBe DualAirPods.ConnectionState.MUSIC
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package eu.darken.capod.pods.core.apple.airpods
|
package eu.darken.capod.pods.core.apple.airpods
|
||||||
|
|
||||||
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualAirPods
|
||||||
|
import eu.darken.capod.pods.core.apple.HasAppleColor
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -32,11 +33,11 @@ class AirPodsGen3Test : BaseAirPodsTest() {
|
|||||||
isRightPodInEar shouldBe false
|
isRightPodInEar shouldBe false
|
||||||
batteryCasePercent shouldBe 0.9f
|
batteryCasePercent shouldBe 0.9f
|
||||||
|
|
||||||
caseLidState shouldBe DualApplePods.LidState.OPEN
|
caseLidState shouldBe DualAirPods.LidState.OPEN
|
||||||
|
|
||||||
connectionState shouldBe DualApplePods.ConnectionState.IDLE
|
state shouldBe DualAirPods.ConnectionState.IDLE
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,11 +55,11 @@ class AirPodsGen3Test : BaseAirPodsTest() {
|
|||||||
isRightPodInEar shouldBe true
|
isRightPodInEar shouldBe true
|
||||||
batteryCasePercent shouldBe null
|
batteryCasePercent shouldBe null
|
||||||
|
|
||||||
caseLidState shouldBe DualApplePods.LidState.NOT_IN_CASE
|
caseLidState shouldBe DualAirPods.LidState.NOT_IN_CASE
|
||||||
|
|
||||||
connectionState shouldBe DualApplePods.ConnectionState.UNKNOWN
|
state shouldBe DualAirPods.ConnectionState.UNKNOWN
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package eu.darken.capod.pods.core.apple.airpods
|
package eu.darken.capod.pods.core.apple.airpods
|
||||||
|
|
||||||
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.HasAppleColor
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -33,7 +33,7 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isLeftPodCharging shouldBe true
|
isLeftPodCharging shouldBe true
|
||||||
batteryCasePercent shouldBe 0.5f
|
batteryCasePercent shouldBe 0.5f
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +50,6 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isRightPodCharging shouldBe false
|
isRightPodCharging shouldBe false
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
batteryCasePercent shouldBe null
|
batteryCasePercent shouldBe null
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,8 +64,6 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isRightPodCharging shouldBe false
|
isRightPodCharging shouldBe false
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
batteryCasePercent shouldBe null
|
batteryCasePercent shouldBe null
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create<AirPodsPro>("0719010e202b668f01000500000000000000000000000000000000") {
|
create<AirPodsPro>("0719010e202b668f01000500000000000000000000000000000000") {
|
||||||
@@ -78,8 +74,6 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isRightPodCharging shouldBe false
|
isRightPodCharging shouldBe false
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
batteryCasePercent shouldBe null
|
batteryCasePercent shouldBe null
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create<AirPodsPro>("0719010e202b668f01000400000000000000000000000000000000") {
|
create<AirPodsPro>("0719010e202b668f01000400000000000000000000000000000000") {
|
||||||
@@ -90,8 +84,6 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isRightPodCharging shouldBe false
|
isRightPodCharging shouldBe false
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
batteryCasePercent shouldBe null
|
batteryCasePercent shouldBe null
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create<AirPodsPro>("0719010e200b668f01000500000000000000000000000000000000") {
|
create<AirPodsPro>("0719010e200b668f01000500000000000000000000000000000000") {
|
||||||
@@ -102,8 +94,6 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isRightPodCharging shouldBe false
|
isRightPodCharging shouldBe false
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
batteryCasePercent shouldBe null
|
batteryCasePercent shouldBe null
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create<AirPodsPro>("0719010e2003668f01000500000000000000000000000000000000") {
|
create<AirPodsPro>("0719010e2003668f01000500000000000000000000000000000000") {
|
||||||
@@ -114,8 +104,6 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isRightPodCharging shouldBe false
|
isRightPodCharging shouldBe false
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
batteryCasePercent shouldBe null
|
batteryCasePercent shouldBe null
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create<AirPodsPro>("0719010e2001668f01000500000000000000000000000000000000") {
|
create<AirPodsPro>("0719010e2001668f01000500000000000000000000000000000000") {
|
||||||
@@ -126,8 +114,6 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isRightPodCharging shouldBe false
|
isRightPodCharging shouldBe false
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
batteryCasePercent shouldBe null
|
batteryCasePercent shouldBe null
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create<AirPodsPro>("0719010e2009668f01000500000000000000000000000000000000") {
|
create<AirPodsPro>("0719010e2009668f01000500000000000000000000000000000000") {
|
||||||
@@ -138,8 +124,6 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isRightPodCharging shouldBe false
|
isRightPodCharging shouldBe false
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
batteryCasePercent shouldBe null
|
batteryCasePercent shouldBe null
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create<AirPodsPro>("0719010e2053669653000500000000000000000000000000000000") {
|
create<AirPodsPro>("0719010e2053669653000500000000000000000000000000000000") {
|
||||||
@@ -150,8 +134,6 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isRightPodCharging shouldBe true
|
isRightPodCharging shouldBe true
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
batteryCasePercent shouldBe 0.6f
|
batteryCasePercent shouldBe 0.6f
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create<AirPodsPro>("0719010e203366a602000500000000000000000000000000000000") {
|
create<AirPodsPro>("0719010e203366a602000500000000000000000000000000000000") {
|
||||||
@@ -162,8 +144,6 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isRightPodCharging shouldBe true
|
isRightPodCharging shouldBe true
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
batteryCasePercent shouldBe 0.6f
|
batteryCasePercent shouldBe 0.6f
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create<AirPodsPro>("0719010e202b768f02000500000000000000000000000000000000") {
|
create<AirPodsPro>("0719010e202b768f02000500000000000000000000000000000000") {
|
||||||
@@ -174,8 +154,6 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isRightPodCharging shouldBe false
|
isRightPodCharging shouldBe false
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
batteryCasePercent shouldBe 0.6f
|
batteryCasePercent shouldBe 0.6f
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,8 +168,6 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
isRightPodCharging shouldBe true
|
isRightPodCharging shouldBe true
|
||||||
isLeftPodCharging shouldBe false
|
isLeftPodCharging shouldBe false
|
||||||
batteryCasePercent shouldBe 0.6f
|
batteryCasePercent shouldBe 0.6f
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package eu.darken.capod.pods.core.apple.beats
|
package eu.darken.capod.pods.core.apple.beats
|
||||||
|
|
||||||
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.HasAppleColor
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -33,7 +33,7 @@ class PowerBeatsProTest : BaseAirPodsTest() {
|
|||||||
isLeftPodCharging shouldBe true
|
isLeftPodCharging shouldBe true
|
||||||
batteryCasePercent shouldBe 0.5f
|
batteryCasePercent shouldBe 0.5f
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-12
@@ -1,7 +1,6 @@
|
|||||||
package eu.darken.capod.pods.core.apple.fakes
|
package eu.darken.capod.pods.core.apple.misc
|
||||||
|
|
||||||
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -25,18 +24,8 @@ class Twsi99999Test : BaseAirPodsTest() {
|
|||||||
batteryRightPodPercent shouldBe 1.0f
|
batteryRightPodPercent shouldBe 1.0f
|
||||||
|
|
||||||
isCaseCharging shouldBe false
|
isCaseCharging shouldBe false
|
||||||
isLeftPodCharging shouldBe true
|
|
||||||
isRightPodCharging shouldBe true
|
|
||||||
|
|
||||||
isLeftPodInEar shouldBe false
|
|
||||||
isRightPodInEar shouldBe false
|
|
||||||
batteryCasePercent shouldBe 0.7f
|
batteryCasePercent shouldBe 0.7f
|
||||||
|
|
||||||
caseLidState shouldBe DualApplePods.LidState.OPEN
|
|
||||||
|
|
||||||
connectionState shouldBe DualApplePods.ConnectionState.UNKNOWN
|
|
||||||
|
|
||||||
deviceColor shouldBe DualApplePods.DeviceColor.WHITE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package eu.darken.capod.pods.core.apple.misc
|
||||||
|
|
||||||
|
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class VarunrAirPodsProTest : BaseAirPodsTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `guessed data`() = runBlockingTest {
|
||||||
|
create<VarunrAirPodsPro>("07 13 01 0E 20 71 AA 37 36 00 10 00 FF 64 FF 00 00 00 00 00 00") {
|
||||||
|
rawPrefix shouldBe 0x01.toUByte()
|
||||||
|
rawDeviceModel shouldBe 0x0E20.toUShort()
|
||||||
|
rawStatus shouldBe 0x71.toUByte()
|
||||||
|
rawPodsBattery shouldBe 0xAA.toUByte()
|
||||||
|
rawFlags shouldBe 0x3.toUShort()
|
||||||
|
rawCaseBattery shouldBe 0x7.toUShort()
|
||||||
|
rawCaseLidState shouldBe 0x36.toUByte()
|
||||||
|
rawDeviceColor shouldBe 0x00.toUByte()
|
||||||
|
rawSuffix shouldBe 0x10.toUByte()
|
||||||
|
|
||||||
|
batteryLeftPodPercent shouldBe 1.0f
|
||||||
|
batteryRightPodPercent shouldBe 1.0f
|
||||||
|
|
||||||
|
isCaseCharging shouldBe false
|
||||||
|
|
||||||
|
batteryCasePercent shouldBe 0.7f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -8,7 +8,7 @@ buildscript {
|
|||||||
'version' : [
|
'version' : [
|
||||||
'major': 1,
|
'major': 1,
|
||||||
'minor': 3,
|
'minor': 3,
|
||||||
'patch': 6,
|
'patch': 8,
|
||||||
'build': 0,
|
'build': 0,
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
# This is the minimum version number required.
|
# This is the minimum version number required.
|
||||||
# Update this, if you use features of a newer version
|
# Update this, if you use features of a newer version
|
||||||
fastlane_version "2.137.0"
|
fastlane_version "2.204.3"
|
||||||
|
|
||||||
default_platform :android
|
default_platform :android
|
||||||
|
|
||||||
|
|||||||
+16
-18
@@ -1,21 +1,15 @@
|
|||||||
fastlane documentation
|
fastlane documentation
|
||||||
================
|
----
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
Make sure you have the latest version of the Xcode command line tools installed:
|
Make sure you have the latest version of the Xcode command line tools installed:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
xcode-select --install
|
xcode-select --install
|
||||||
```
|
```
|
||||||
|
|
||||||
Install _fastlane_ using
|
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||||
|
|
||||||
```
|
|
||||||
[sudo] gem install fastlane -NV
|
|
||||||
```
|
|
||||||
|
|
||||||
or alternatively using `brew cask install fastlane`
|
|
||||||
|
|
||||||
# Available Actions
|
# Available Actions
|
||||||
|
|
||||||
@@ -23,24 +17,28 @@ or alternatively using `brew cask install fastlane`
|
|||||||
|
|
||||||
### android beta
|
### android beta
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[bundle exec] fastlane android beta
|
||||||
```
|
```
|
||||||
fastlane android beta
|
|
||||||
```
|
|
||||||
|
|
||||||
### android production
|
### android production
|
||||||
|
|
||||||
```
|
```sh
|
||||||
fastlane android production
|
[bundle exec] fastlane android production
|
||||||
```
|
```
|
||||||
|
|
||||||
### android listing_only
|
### android listing_only
|
||||||
|
|
||||||
```
|
```sh
|
||||||
fastlane android listing_only
|
[bundle exec] fastlane android listing_only
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. More
|
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||||
information about fastlane can be found on [fastlane.tools](https://fastlane.tools). The documentation of fastlane can
|
|
||||||
be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
|
||||||
|
|
||||||
|
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
CAPod - Acompanyant per als AirPods
|
CAPod - Assistent pels AirPods
|
||||||
@@ -1 +1 @@
|
|||||||
CAPod - Aplicación complementaria para AirPods
|
CAPod - Asistente para AirPods
|
||||||
@@ -1 +1 @@
|
|||||||
A CAPod az AirPods kísérőalkalmazása
|
CAPod - Companion for AirPods
|
||||||
@@ -1 +1 @@
|
|||||||
CAPod - Pendamping untuk AirPods
|
CAPod - Pendamping AirPods
|
||||||
@@ -1 +1 @@
|
|||||||
CAPod - Companion app per le Airpods
|
CAPod - Companion for AirPods
|
||||||
@@ -1 +1 @@
|
|||||||
CAPod - Complemento para AirPods
|
CAPod - Companion for AirPods
|
||||||
@@ -1 +1 @@
|
|||||||
CAPod - Bạn đồng hành cho AirPods
|
CAPod - Companion for AirPods
|
||||||
@@ -6,7 +6,7 @@ CAPod 是为 AirPods 提供配套功能的一个应用。
|
|||||||
* 有关连接、麦克风和充电仓的附加信息。
|
* 有关连接、麦克风和充电仓的附加信息。
|
||||||
* 能够接收和显示位于附近的全部设备。
|
* 能够接收和显示位于附近的全部设备。
|
||||||
* 佩戴/取下时自动开始/暂停音乐播放。
|
* 佩戴/取下时自动开始/暂停音乐播放。
|
||||||
* Automatically connect phone and AirPods.
|
* 自动连接手机与 AirPods。
|
||||||
* 充电仓打开时显示弹窗。
|
* 充电仓打开时显示弹窗。
|
||||||
|
|
||||||
CAPod 是一款免费共享软件。 某些功能需在应用内购买。
|
CAPod 是一款免费共享软件。 某些功能需在应用内购买。
|
||||||
|
|||||||
@@ -7,5 +7,9 @@ rm -rv ./metadata/android/sq-AL
|
|||||||
rm -rv ./metadata/android/uz
|
rm -rv ./metadata/android/uz
|
||||||
rm -rv ./metadata/android/pcm-NG
|
rm -rv ./metadata/android/pcm-NG
|
||||||
rm -rv ./metadata/android/tl-PH
|
rm -rv ./metadata/android/tl-PH
|
||||||
|
rm -rv ./metadata/android/ku-TR
|
||||||
|
rm -rv ./metadata/android/kmr-TR
|
||||||
|
rm -rv ./metadata/android/ur-IN
|
||||||
|
rm -rv ./metadata/android/zu
|
||||||
find ./metadata/android -empty -type d -delete
|
find ./metadata/android -empty -type d -delete
|
||||||
exit 0
|
exit 0
|
||||||
Reference in New Issue
Block a user