diff --git a/README.md b/README.md
index ae8e1b26..8174352f 100644
--- a/README.md
+++ b/README.md
@@ -19,15 +19,6 @@ Supported models:
* [Google Play](https://play.google.com/store/apps/details?id=eu.darken.capod)
* [GitHub](https://github.com/d4rken/android-airpods-companion/releases/latest)
-## Use CAPod as a library
-
-AirPods use Apple's Continuity Protocol to communicate, specifically the Proximity Pairing Message type. If you want to
-build your own app based on this, you can include it like this:
-
-```groovy
-// TODO
-```
-
## Get help
* [Github Issues](https://github.com/d4rken/android-airpods-companion/issues)
@@ -42,5 +33,5 @@ CAPod's code is available under a GPL v3 license, this excludes:
* CAPod icons, logos, mascots and marketing materials.
* CAPod animations and videos.
* CAPod documentation.
-* Google Play store screenshots.
-* Google Play store texts & descriptions.
\ No newline at end of file
+* Google Play screenshots.
+* Google Play texts & descriptions.
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 9014adf4..fa550a62 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,10 +12,14 @@ def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim
def buildTime = new Date().format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("GMT+1"))
android {
- def packageName = "eu.darken.cap"
+ def packageName = "eu.darken.capod"
compileSdkVersion buildConfig.compileSdk
+ Properties bugsnagProps = new Properties()
+ def bugsnagPropsFile = new File(System.properties['user.home'], ".appconfig/${packageName}/bugsnag.properties")
+ if (bugsnagPropsFile.canRead()) bugsnagProps.load(new FileInputStream(bugsnagPropsFile))
+
defaultConfig {
applicationId "${packageName}"
@@ -29,6 +33,8 @@ android {
buildConfigField "String", "GITSHA", "\"${gitSha}\""
buildConfigField "String", "BUILDTIME", "\"${buildTime}\""
+
+ manifestPlaceholders = [bugsnagApiKey: "fake"]
}
signingConfigs {
@@ -51,12 +57,12 @@ android {
buildTypes {
def proguardRulesRelease = fileTree(dir: "../proguard", include: ["*.pro"]).asList().toArray()
debug {
- ext.enableBugsnag = false
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
proguardFiles proguardRulesRelease
proguardFiles 'proguard-rules-debug.pro'
+ manifestPlaceholders = [bugsnagApiKey: bugsnagProps.getProperty("bugsnag.apikey", "")]
}
release {
signingConfig signingConfigs.release
@@ -64,11 +70,11 @@ android {
abortOnError true
fatal 'StopShip'
}
- ext.enableBugsnag = true
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
proguardFiles proguardRulesRelease
+ manifestPlaceholders = [bugsnagApiKey: bugsnagProps.getProperty("bugsnag.apikey", "")]
}
applicationVariants.all { variant ->
if (variant.buildType.name == "debug") {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a6b2b9a0..ad08eb1c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,15 +3,33 @@
xmlns:tools="http://schemas.android.com/tools"
package="eu.darken.capod">
-
-
+
+
+
-
-
-
+
+
+
+
+
+
+
+
@@ -31,7 +49,7 @@
@@ -55,10 +73,10 @@
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/App.kt b/app/src/main/java/eu/darken/capod/App.kt
index 5d11498f..c39631ad 100644
--- a/app/src/main/java/eu/darken/capod/App.kt
+++ b/app/src/main/java/eu/darken/capod/App.kt
@@ -6,7 +6,7 @@ import androidx.work.Configuration
import com.getkeepsafe.relinker.ReLinker
import dagger.hilt.android.HiltAndroidApp
import eu.darken.capod.common.coroutine.AppScope
-import eu.darken.capod.common.debug.bugreporting.BugReporter
+import eu.darken.capod.common.debug.autoreport.AutoReporting
import eu.darken.capod.common.debug.logging.*
import eu.darken.capod.monitor.core.worker.MonitorControl
import kotlinx.coroutines.CoroutineScope
@@ -17,7 +17,7 @@ import javax.inject.Inject
open class App : Application(), Configuration.Provider {
@Inject lateinit var workerFactory: HiltWorkerFactory
- @Inject lateinit var bugReporter: BugReporter
+ @Inject lateinit var autoReporting: AutoReporting
@Inject lateinit var monitorControl: MonitorControl
@Inject @AppScope lateinit var appScope: CoroutineScope
@@ -29,7 +29,7 @@ open class App : Application(), Configuration.Provider {
.log { message -> log(TAG) { "ReLinker: $message" } }
.loadLibrary(this, "bugsnag-plugin-android-anr")
- bugReporter.setup()
+ autoReporting.setup()
log(TAG) { "onCreate() done! ${Exception().asLog()}" }
diff --git a/app/src/main/java/eu/darken/capod/common/BuildWrap.kt b/app/src/main/java/eu/darken/capod/common/BuildWrap.kt
index 47fcf195..9967862e 100644
--- a/app/src/main/java/eu/darken/capod/common/BuildWrap.kt
+++ b/app/src/main/java/eu/darken/capod/common/BuildWrap.kt
@@ -14,3 +14,5 @@ object BuildWrap {
}
fun hasApiLevel(level: Int): Boolean = BuildWrap.VERSION.SDK_INT >= level
+
+fun withinApiLevel(start: Int, end: Int): Boolean = BuildWrap.VERSION.SDK_INT in start..end
diff --git a/app/src/main/java/eu/darken/capod/common/bluetooth/BleScanner.kt b/app/src/main/java/eu/darken/capod/common/bluetooth/BleScanner.kt
index b554cc94..aaca78d3 100644
--- a/app/src/main/java/eu/darken/capod/common/bluetooth/BleScanner.kt
+++ b/app/src/main/java/eu/darken/capod/common/bluetooth/BleScanner.kt
@@ -10,7 +10,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.common.debug.logging.Logging.Priority.*
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
-import eu.darken.capod.pods.core.airpods.ProximityPairing
+import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
diff --git a/app/src/main/java/eu/darken/capod/common/debug/bugreporting/Bugs.kt b/app/src/main/java/eu/darken/capod/common/debug/Bugs.kt
similarity index 86%
rename from app/src/main/java/eu/darken/capod/common/debug/bugreporting/Bugs.kt
rename to app/src/main/java/eu/darken/capod/common/debug/Bugs.kt
index 508b0ca3..09b7391c 100644
--- a/app/src/main/java/eu/darken/capod/common/debug/bugreporting/Bugs.kt
+++ b/app/src/main/java/eu/darken/capod/common/debug/Bugs.kt
@@ -1,4 +1,4 @@
-package eu.darken.capod.common.debug.bugreporting
+package eu.darken.capod.common.debug
import com.bugsnag.android.Bugsnag
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
@@ -8,7 +8,7 @@ import eu.darken.capod.common.debug.logging.logTag
object Bugs {
var ready = false
- fun report(exception: Exception) {
+ fun report(exception: Throwable) {
log(TAG, VERBOSE) { "Reporting $exception" }
if (!ready) {
log(TAG, WARN) { "Bug tracking not initialized yet." }
diff --git a/app/src/main/java/eu/darken/capod/common/debug/bugreporting/BugReporter.kt b/app/src/main/java/eu/darken/capod/common/debug/autoreport/AutoReporting.kt
similarity index 74%
rename from app/src/main/java/eu/darken/capod/common/debug/bugreporting/BugReporter.kt
rename to app/src/main/java/eu/darken/capod/common/debug/autoreport/AutoReporting.kt
index 35c6b20c..104be984 100644
--- a/app/src/main/java/eu/darken/capod/common/debug/bugreporting/BugReporter.kt
+++ b/app/src/main/java/eu/darken/capod/common/debug/autoreport/AutoReporting.kt
@@ -1,13 +1,14 @@
-package eu.darken.capod.common.debug.bugreporting
+package eu.darken.capod.common.debug.autoreport
import android.content.Context
import com.bugsnag.android.Bugsnag
import com.bugsnag.android.Configuration
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.common.InstallId
-import eu.darken.capod.common.debug.bugsnag.BugsnagErrorHandler
-import eu.darken.capod.common.debug.bugsnag.BugsnagLogger
-import eu.darken.capod.common.debug.bugsnag.NOPBugsnagErrorHandler
+import eu.darken.capod.common.debug.Bugs
+import eu.darken.capod.common.debug.autoreport.bugsnag.BugsnagErrorHandler
+import eu.darken.capod.common.debug.autoreport.bugsnag.BugsnagLogger
+import eu.darken.capod.common.debug.autoreport.bugsnag.NOPBugsnagErrorHandler
import eu.darken.capod.common.debug.logging.Logging
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
@@ -16,9 +17,9 @@ import javax.inject.Provider
import javax.inject.Singleton
@Singleton
-class BugReporter @Inject constructor(
+class AutoReporting @Inject constructor(
@ApplicationContext private val context: Context,
- private val bugReportSettings: BugReportSettings,
+ private val debugSettings: DebugSettings,
private val installId: InstallId,
private val bugsnagLogger: Provider,
private val bugsnagErrorHandler: Provider,
@@ -26,12 +27,12 @@ class BugReporter @Inject constructor(
) {
fun setup() {
- val isEnabled = bugReportSettings.isEnabled.value
+ val isEnabled = debugSettings.isAutoReportEnabled.value
log(TAG) { "setup(): isEnabled=$isEnabled" }
try {
val bugsnagConfig = Configuration.load(context).apply {
- if (bugReportSettings.isEnabled.value) {
+ if (debugSettings.isAutoReportEnabled.value) {
Logging.install(bugsnagLogger.get())
setUser(installId.id, null, null)
autoTrackSessions = true
@@ -52,6 +53,6 @@ class BugReporter @Inject constructor(
}
companion object {
- private val TAG = logTag("BugReporter")
+ private val TAG = logTag("Debug", "AutoReport")
}
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/common/debug/bugreporting/BugReportSettings.kt b/app/src/main/java/eu/darken/capod/common/debug/autoreport/DebugSettings.kt
similarity index 68%
rename from app/src/main/java/eu/darken/capod/common/debug/bugreporting/BugReportSettings.kt
rename to app/src/main/java/eu/darken/capod/common/debug/autoreport/DebugSettings.kt
index c6aac9df..6c758cf2 100644
--- a/app/src/main/java/eu/darken/capod/common/debug/bugreporting/BugReportSettings.kt
+++ b/app/src/main/java/eu/darken/capod/common/debug/autoreport/DebugSettings.kt
@@ -1,4 +1,4 @@
-package eu.darken.capod.common.debug.bugreporting
+package eu.darken.capod.common.debug.autoreport
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
@@ -7,7 +7,7 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
-class BugReportSettings @Inject constructor(
+class DebugSettings @Inject constructor(
@ApplicationContext private val context: Context,
) {
@@ -15,6 +15,6 @@ class BugReportSettings @Inject constructor(
context.getSharedPreferences("bugreport_settings", Context.MODE_PRIVATE)
}
- val isEnabled = prefs.createFlowPreference("bugreport.automatic.enabled", true)
+ val isAutoReportEnabled = prefs.createFlowPreference("bugreport.automatic.enabled", true)
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/common/debug/bugsnag/BugsnagErrorHandler.kt b/app/src/main/java/eu/darken/capod/common/debug/autoreport/bugsnag/BugsnagErrorHandler.kt
similarity index 85%
rename from app/src/main/java/eu/darken/capod/common/debug/bugsnag/BugsnagErrorHandler.kt
rename to app/src/main/java/eu/darken/capod/common/debug/autoreport/bugsnag/BugsnagErrorHandler.kt
index 3e29bf6d..c99d261a 100644
--- a/app/src/main/java/eu/darken/capod/common/debug/bugsnag/BugsnagErrorHandler.kt
+++ b/app/src/main/java/eu/darken/capod/common/debug/autoreport/bugsnag/BugsnagErrorHandler.kt
@@ -1,4 +1,4 @@
-package eu.darken.capod.common.debug.bugsnag
+package eu.darken.capod.common.debug.autoreport.bugsnag
import android.annotation.SuppressLint
import android.content.Context
@@ -7,6 +7,8 @@ import com.bugsnag.android.Event
import com.bugsnag.android.OnErrorCallback
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.BuildConfig
+import eu.darken.capod.common.BuildConfigWrap
+import eu.darken.capod.common.debug.autoreport.DebugSettings
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
import eu.darken.capod.common.debug.logging.asLog
import eu.darken.capod.common.debug.logging.log
@@ -17,6 +19,7 @@ import javax.inject.Singleton
class BugsnagErrorHandler @Inject constructor(
@ApplicationContext private val context: Context,
private val bugsnagLogger: BugsnagLogger,
+ private val debugSettings: DebugSettings,
) : OnErrorCallback {
override fun onError(event: Event): Boolean {
@@ -29,7 +32,7 @@ class BugsnagErrorHandler @Inject constructor(
context.tryFormattedSignature()?.let { event.addMetadata(tab, "signatures", it) }
}
- return !BuildConfig.DEBUG
+ return debugSettings.isAutoReportEnabled.value && !BuildConfigWrap.DEBUG
}
companion object {
diff --git a/app/src/main/java/eu/darken/capod/common/debug/bugsnag/BugsnagLogger.kt b/app/src/main/java/eu/darken/capod/common/debug/autoreport/bugsnag/BugsnagLogger.kt
similarity index 96%
rename from app/src/main/java/eu/darken/capod/common/debug/bugsnag/BugsnagLogger.kt
rename to app/src/main/java/eu/darken/capod/common/debug/autoreport/bugsnag/BugsnagLogger.kt
index b77489b6..d80b70cf 100644
--- a/app/src/main/java/eu/darken/capod/common/debug/bugsnag/BugsnagLogger.kt
+++ b/app/src/main/java/eu/darken/capod/common/debug/autoreport/bugsnag/BugsnagLogger.kt
@@ -1,4 +1,4 @@
-package eu.darken.capod.common.debug.bugsnag
+package eu.darken.capod.common.debug.autoreport.bugsnag
import com.bugsnag.android.Event
import eu.darken.capod.common.debug.logging.Logging
diff --git a/app/src/main/java/eu/darken/capod/common/debug/bugsnag/NOPBugsnagErrorHandler.kt b/app/src/main/java/eu/darken/capod/common/debug/autoreport/bugsnag/NOPBugsnagErrorHandler.kt
similarity index 90%
rename from app/src/main/java/eu/darken/capod/common/debug/bugsnag/NOPBugsnagErrorHandler.kt
rename to app/src/main/java/eu/darken/capod/common/debug/autoreport/bugsnag/NOPBugsnagErrorHandler.kt
index d61d3114..fa10e669 100644
--- a/app/src/main/java/eu/darken/capod/common/debug/bugsnag/NOPBugsnagErrorHandler.kt
+++ b/app/src/main/java/eu/darken/capod/common/debug/autoreport/bugsnag/NOPBugsnagErrorHandler.kt
@@ -1,4 +1,4 @@
-package eu.darken.capod.common.debug.bugsnag
+package eu.darken.capod.common.debug.autoreport.bugsnag
import com.bugsnag.android.Event
import com.bugsnag.android.OnErrorCallback
diff --git a/app/src/main/java/eu/darken/capod/common/permissions/Permission.kt b/app/src/main/java/eu/darken/capod/common/permissions/Permission.kt
index 7b315c85..ee75bc3f 100644
--- a/app/src/main/java/eu/darken/capod/common/permissions/Permission.kt
+++ b/app/src/main/java/eu/darken/capod/common/permissions/Permission.kt
@@ -6,13 +6,22 @@ import android.os.Build
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import eu.darken.capod.R
+import eu.darken.capod.common.withinApiLevel
enum class Permission(
val minApiLevel: Int,
+ val maxApiLevel: Int = Int.MAX_VALUE,
@StringRes val labelRes: Int,
@StringRes val descriptionRes: Int,
val permissionId: String,
) {
+ BLUETOOTH(
+ minApiLevel = Build.VERSION_CODES.BASE,
+ maxApiLevel = Build.VERSION_CODES.R,
+ labelRes = R.string.permission_bluetooth_label,
+ descriptionRes = R.string.permission_bluetooth_description,
+ permissionId = "android.permission.BLUETOOTH",
+ ),
BLUETOOTH_CONNECT(
minApiLevel = Build.VERSION_CODES.S,
labelRes = R.string.permission_bluetooth_connect_label,
@@ -24,9 +33,17 @@ enum class Permission(
labelRes = R.string.permission_bluetooth_scan_label,
descriptionRes = R.string.permission_bluetooth_scan_description,
permissionId = "android.permission.BLUETOOTH_SCAN",
+ ),
+ ACCESS_FINE_LOCATION(
+ minApiLevel = Build.VERSION_CODES.S,
+ maxApiLevel = Build.VERSION_CODES.R,
+ labelRes = R.string.permission_access_fine_location_label,
+ descriptionRes = R.string.permission_access_fine_location_description,
+ permissionId = "android.permission.ACCESS_FINE_LOCATION",
)
}
-fun Permission.isGranted(context: Context): Boolean {
- return ContextCompat.checkSelfPermission(context, permissionId) == PackageManager.PERMISSION_GRANTED
+fun Permission.isGrantedOrNotRequired(context: Context): Boolean = when {
+ !withinApiLevel(minApiLevel, maxApiLevel) -> true
+ else -> ContextCompat.checkSelfPermission(context, permissionId) == PackageManager.PERMISSION_GRANTED
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/main/ui/MainFragmentVM.kt b/app/src/main/java/eu/darken/capod/main/ui/MainFragmentVM.kt
deleted file mode 100644
index 50a22ed3..00000000
--- a/app/src/main/java/eu/darken/capod/main/ui/MainFragmentVM.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-package eu.darken.capod.main.ui
-
-import android.content.Context
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.SavedStateHandle
-import dagger.hilt.android.lifecycle.HiltViewModel
-import dagger.hilt.android.qualifiers.ApplicationContext
-import eu.darken.capod.common.coroutine.DispatcherProvider
-import eu.darken.capod.common.debug.recording.core.RecorderModule
-import eu.darken.capod.common.hasApiLevel
-import eu.darken.capod.common.livedata.SingleLiveEvent
-import eu.darken.capod.common.permissions.Permission
-import eu.darken.capod.common.permissions.isGranted
-import eu.darken.capod.common.uix.ViewModel3
-import eu.darken.capod.main.ui.cards.PermissionCardVH
-import eu.darken.capod.main.ui.cards.ToggleCardVH
-import kotlinx.coroutines.flow.*
-import java.util.*
-import javax.inject.Inject
-
-@HiltViewModel
-class MainFragmentVM @Inject constructor(
- handle: SavedStateHandle,
- @ApplicationContext private val context: Context,
- dispatcherProvider: DispatcherProvider,
- private val recorderModule: RecorderModule,
-) : ViewModel3(dispatcherProvider = dispatcherProvider) {
-
-
- private val enabledState: Flow = flow {
- emit(false)
- }
-
- private val permissionCheckTrigger = MutableStateFlow(UUID.randomUUID())
- private val requiredPermissions: Flow> = permissionCheckTrigger.map {
- Permission.values()
- .filter { hasApiLevel(it.minApiLevel) && !it.isGranted(context) }
- }
-
- val requestPermissionevent = SingleLiveEvent()
-
- val listItems: LiveData> = combine(
- enabledState,
- requiredPermissions
- ) { state, permissions ->
- val items = mutableListOf()
- ToggleCardVH.Item(
- isEnabled = state,
- onToggle = {
-
- }
- ).run { items.add(this) }
-
- permissions
- .map {
- PermissionCardVH.Item(
- permission = it,
- onRequest = { requestPermissionevent.postValue(it) }
- )
- }
- .forEach { items.add(it) }
-
- items
- }.asLiveData2()
-
- fun onPermissionResult(granted: Boolean) {
- if (granted) permissionCheckTrigger.value = UUID.randomUUID()
- }
-
- fun toggleDebugLog() = launch {
- recorderModule.startRecorder()
- }
-
- fun goToSettings() = launch {
-
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/main/ui/cards/ToggleCardVH.kt b/app/src/main/java/eu/darken/capod/main/ui/cards/ToggleCardVH.kt
deleted file mode 100644
index 90d2fc84..00000000
--- a/app/src/main/java/eu/darken/capod/main/ui/cards/ToggleCardVH.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package eu.darken.capod.main.ui.cards
-
-import android.view.ViewGroup
-import eu.darken.capod.R
-import eu.darken.capod.databinding.MainToggleItemBinding
-import eu.darken.capod.main.ui.MainAdapter
-
-class ToggleCardVH(parent: ViewGroup) :
- MainAdapter.BaseVH(
- R.layout.main_toggle_item,
- parent
- ) {
-
- override val viewBinding = lazy {
- MainToggleItemBinding.bind(itemView)
- }
-
- override val onBindData: MainToggleItemBinding.(
- item: Item,
- payloads: List
- ) -> Unit = { item, _ ->
- toggleAction.text = when (item.isEnabled) {
- true -> "Disable"
- false -> "Enable"
- }
- itemView.setOnClickListener { item.onToggle() }
- }
-
- data class Item(
- val isEnabled: Boolean,
- val onToggle: () -> Unit
- ) : MainAdapter.Item {
- override val stableId: Long = this.javaClass.hashCode().toLong()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/main/ui/MainAdapter.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewAdapter.kt
similarity index 63%
rename from app/src/main/java/eu/darken/capod/main/ui/MainAdapter.kt
rename to app/src/main/java/eu/darken/capod/main/ui/overview/OverviewAdapter.kt
index 36d83ac8..fee8dd28 100644
--- a/app/src/main/java/eu/darken/capod/main/ui/MainAdapter.kt
+++ b/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewAdapter.kt
@@ -1,4 +1,4 @@
-package eu.darken.capod.main.ui
+package eu.darken.capod.main.ui.overview
import android.view.ViewGroup
import androidx.annotation.LayoutRes
@@ -11,20 +11,22 @@ import eu.darken.capod.common.lists.differ.setupDiffer
import eu.darken.capod.common.lists.modular.ModularAdapter
import eu.darken.capod.common.lists.modular.mods.DataBinderMod
import eu.darken.capod.common.lists.modular.mods.TypedVHCreatorMod
-import eu.darken.capod.main.ui.cards.PermissionCardVH
-import eu.darken.capod.main.ui.cards.ToggleCardVH
+import eu.darken.capod.main.ui.overview.cards.DualApplePodsCardVH
+import eu.darken.capod.main.ui.overview.cards.PermissionCardVH
+import eu.darken.capod.main.ui.overview.cards.UnknownPodDeviceCardVH
import javax.inject.Inject
-class MainAdapter @Inject constructor() :
- ModularAdapter>(),
- HasAsyncDiffer {
+class OverviewAdapter @Inject constructor() :
+ ModularAdapter>(),
+ HasAsyncDiffer {
override val asyncDiffer: AsyncDiffer<*, Item> = setupDiffer()
init {
modules.add(DataBinderMod(data))
- modules.add(TypedVHCreatorMod({ data[it] is ToggleCardVH.Item }) { ToggleCardVH(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 UnknownPodDeviceCardVH.Item }) { UnknownPodDeviceCardVH(it) })
}
override fun getItemCount(): Int = data.size
diff --git a/app/src/main/java/eu/darken/capod/main/ui/MainFragment.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewFragment.kt
similarity index 91%
rename from app/src/main/java/eu/darken/capod/main/ui/MainFragment.kt
rename to app/src/main/java/eu/darken/capod/main/ui/overview/OverviewFragment.kt
index 55b99ada..60ff673e 100644
--- a/app/src/main/java/eu/darken/capod/main/ui/MainFragment.kt
+++ b/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewFragment.kt
@@ -1,4 +1,4 @@
-package eu.darken.capod.main.ui
+package eu.darken.capod.main.ui.overview
import android.os.Bundle
import android.view.View
@@ -17,13 +17,13 @@ import eu.darken.capod.databinding.MainFragmentBinding
import javax.inject.Inject
@AndroidEntryPoint
-class MainFragment : Fragment3(R.layout.main_fragment) {
+class OverviewFragment : Fragment3(R.layout.main_fragment) {
- override val vm: MainFragmentVM by viewModels()
+ override val vm: OverviewFragmentVM by viewModels()
override val ui: MainFragmentBinding by viewBinding()
@Inject
- lateinit var adapter: MainAdapter
+ lateinit var adapter: OverviewAdapter
lateinit var permissionLauncher: ActivityResultLauncher
@@ -37,10 +37,10 @@ class MainFragment : Fragment3(R.layout.main_fragment) {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-
ui.apply {
list.setupDefaults(adapter)
}
+
ui.toolbar.apply {
subtitle = BuildConfigWrap.VERSION_DESCRIPTION
setOnMenuItemClickListener {
diff --git a/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewFragmentVM.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewFragmentVM.kt
new file mode 100644
index 00000000..3a0d225b
--- /dev/null
+++ b/app/src/main/java/eu/darken/capod/main/ui/overview/OverviewFragmentVM.kt
@@ -0,0 +1,107 @@
+package eu.darken.capod.main.ui.overview
+
+import android.content.Context
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.SavedStateHandle
+import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.hilt.android.qualifiers.ApplicationContext
+import eu.darken.capod.common.coroutine.DispatcherProvider
+import eu.darken.capod.common.debug.recording.core.RecorderModule
+import eu.darken.capod.common.livedata.SingleLiveEvent
+import eu.darken.capod.common.permissions.Permission
+import eu.darken.capod.common.permissions.isGrantedOrNotRequired
+import eu.darken.capod.common.uix.ViewModel3
+import eu.darken.capod.main.ui.overview.cards.DualApplePodsCardVH
+import eu.darken.capod.main.ui.overview.cards.PermissionCardVH
+import eu.darken.capod.main.ui.overview.cards.UnknownPodDeviceCardVH
+import eu.darken.capod.monitor.core.PodMonitor
+import eu.darken.capod.monitor.core.worker.MonitorControl
+import eu.darken.capod.pods.core.PodDevice
+import eu.darken.capod.pods.core.airpods.DualApplePods
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.*
+import java.util.*
+import javax.inject.Inject
+
+@HiltViewModel
+class OverviewFragmentVM @Inject constructor(
+ handle: SavedStateHandle,
+ @ApplicationContext private val context: Context,
+ dispatcherProvider: DispatcherProvider,
+ private val recorderModule: RecorderModule,
+ private val monitorControl: MonitorControl,
+ private val podMonitor: PodMonitor,
+) : ViewModel3(dispatcherProvider = dispatcherProvider) {
+
+
+ private val enabledState: Flow = flow {
+ emit(false)
+ }
+
+ private val permissionCheckTrigger = MutableStateFlow(UUID.randomUUID())
+ private val requiredPermissions: Flow> = permissionCheckTrigger
+ .map {
+ Permission.values().filter { !it.isGrantedOrNotRequired(context) }
+ }
+ .onEach {
+ if (it.isEmpty()) {
+ monitorControl.startMonitor()
+ }
+ }
+
+ val requestPermissionevent = SingleLiveEvent()
+
+ private val pods: Flow> = requiredPermissions
+ .flatMapLatest {
+ if (it.isEmpty()) {
+ podMonitor.pods
+ } else {
+ channelFlow {
+ send(emptyList())
+ awaitClose()
+ }
+ }
+ }
+
+ val listItems: LiveData> = combine(
+ enabledState,
+ requiredPermissions,
+ pods
+ ) { state, permissions, pods ->
+ val items = mutableListOf()
+
+ permissions
+ .map {
+ PermissionCardVH.Item(
+ permission = it,
+ onRequest = { requestPermissionevent.postValue(it) }
+ )
+ }
+ .forEach { items.add(it) }
+
+ pods
+ .sortedBy { it.rssi }
+ .map {
+ when (it) {
+ is DualApplePods -> DualApplePodsCardVH.Item(it)
+ else -> UnknownPodDeviceCardVH.Item(it)
+ }
+ }
+ .run { items.addAll(this) }
+
+ items
+ }.asLiveData2()
+
+ fun onPermissionResult(granted: Boolean) {
+ if (granted) permissionCheckTrigger.value = UUID.randomUUID()
+ }
+
+ fun toggleDebugLog() = launch {
+ recorderModule.startRecorder()
+ }
+
+ fun goToSettings() = launch {
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/DualApplePodsCardVH.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/DualApplePodsCardVH.kt
new file mode 100644
index 00000000..ef2d7c05
--- /dev/null
+++ b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/DualApplePodsCardVH.kt
@@ -0,0 +1,90 @@
+package eu.darken.capod.main.ui.overview.cards
+
+import android.view.ViewGroup
+import eu.darken.capod.R
+import eu.darken.capod.databinding.OverviewPodsAppleDualItemBinding
+import eu.darken.capod.main.ui.overview.OverviewAdapter
+import eu.darken.capod.pods.core.DualPods
+import eu.darken.capod.pods.core.airpods.DualApplePods
+import eu.darken.capod.pods.core.airpods.DualApplePods.DeviceColor
+import eu.darken.capod.pods.core.airpods.DualApplePods.LidState
+import eu.darken.capod.pods.core.getBatteryCase
+import eu.darken.capod.pods.core.getBatteryLeftPod
+import eu.darken.capod.pods.core.getBatteryRightPod
+
+class DualApplePodsCardVH(parent: ViewGroup) :
+ OverviewAdapter.BaseVH(
+ R.layout.overview_pods_apple_dual_item,
+ parent
+ ) {
+
+ override val viewBinding = lazy {
+ OverviewPodsAppleDualItemBinding.bind(itemView)
+ }
+
+ override val onBindData: OverviewPodsAppleDualItemBinding.(
+ item: Item,
+ payloads: List
+ ) -> Unit = { item, _ ->
+ val device = item.device
+
+ name.apply {
+ val sb = StringBuilder(device.getLabel(context))
+ if (device.deviceColor != DeviceColor.UNKNOWN) {
+ sb.append(" (${device.getDeviceColorLabel(context)})")
+ }
+ text = sb
+ }
+
+ reception.text = "${device.rssi} (RSSI)"
+
+ podLeft.apply {
+ val sb = StringBuilder(context.getString(R.string.pods_dual_left_label))
+ sb.append("\n").append(device.getBatteryLeftPod(context))
+ when {
+ device.isLeftPodCharging -> sb.append("\n").append("Charging")
+ device.isLeftPodInEar -> sb.append("\n").append("In ear")
+ else -> {}
+ }
+ text = sb
+ }
+
+ podRight.apply {
+ val sb = StringBuilder("Right pod")
+ sb.append("\n").append(device.getBatteryRightPod(context))
+ when {
+ device.isRightPodCharging -> sb.append("\n").append("Charging")
+ device.isRightPodInEar -> sb.append("\n").append("In ear")
+ else -> {}
+ }
+ text = sb
+ }
+
+ when (device.microPhonePod) {
+ DualPods.Pod.LEFT -> podLeft.append("\n(Microphone)")
+ DualPods.Pod.RIGHT -> podRight.append("\n(Microphone)")
+ }
+
+ podCase.apply {
+ val sb = StringBuilder("Case")
+ sb.append("\n").append(device.getBatteryCase(context))
+ if (device.isCaseCharging) sb.append("\n").append("Charging")
+ when (device.caseLidState) {
+ LidState.OPEN -> sb.append("\n").append(context.getString(R.string.pods_case_status_open_label))
+ LidState.CLOSED -> sb.append("\n").append(context.getString(R.string.pods_case_status_closed_label))
+ LidState.NOT_IN_CASE,
+ LidState.UNKNOWN -> {
+ }
+ }
+ text = sb
+ }
+
+ status.text = getString(R.string.pods_status_x_label, device.getConnectionStateLabel(context))
+ }
+
+ data class Item(
+ val device: DualApplePods
+ ) : OverviewAdapter.Item {
+ override val stableId: Long = this.javaClass.hashCode().toLong()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/main/ui/cards/PermissionCardVH.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/PermissionCardVH.kt
similarity index 80%
rename from app/src/main/java/eu/darken/capod/main/ui/cards/PermissionCardVH.kt
rename to app/src/main/java/eu/darken/capod/main/ui/overview/cards/PermissionCardVH.kt
index f47fac9d..21b179ff 100644
--- a/app/src/main/java/eu/darken/capod/main/ui/cards/PermissionCardVH.kt
+++ b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/PermissionCardVH.kt
@@ -1,13 +1,13 @@
-package eu.darken.capod.main.ui.cards
+package eu.darken.capod.main.ui.overview.cards
import android.view.ViewGroup
import eu.darken.capod.R
import eu.darken.capod.common.permissions.Permission
import eu.darken.capod.databinding.MainPermissionItemBinding
-import eu.darken.capod.main.ui.MainAdapter
+import eu.darken.capod.main.ui.overview.OverviewAdapter
class PermissionCardVH(parent: ViewGroup) :
- MainAdapter.BaseVH(
+ OverviewAdapter.BaseVH(
R.layout.main_permission_item,
parent
) {
@@ -28,7 +28,7 @@ class PermissionCardVH(parent: ViewGroup) :
data class Item(
val permission: Permission,
val onRequest: (Permission) -> Unit
- ) : MainAdapter.Item {
+ ) : OverviewAdapter.Item {
override val stableId: Long = this.javaClass.hashCode().toLong()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/UnknownPodDeviceCardVH.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/UnknownPodDeviceCardVH.kt
new file mode 100644
index 00000000..48582685
--- /dev/null
+++ b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/UnknownPodDeviceCardVH.kt
@@ -0,0 +1,32 @@
+package eu.darken.capod.main.ui.overview.cards
+
+import android.view.ViewGroup
+import eu.darken.capod.R
+import eu.darken.capod.databinding.OverviewPodsUnknownItemBinding
+import eu.darken.capod.main.ui.overview.OverviewAdapter
+import eu.darken.capod.pods.core.PodDevice
+
+class UnknownPodDeviceCardVH(parent: ViewGroup) :
+ OverviewAdapter.BaseVH(
+ R.layout.overview_pods_unknown_item,
+ parent
+ ) {
+
+ override val viewBinding = lazy {
+ OverviewPodsUnknownItemBinding.bind(itemView)
+ }
+
+ override val onBindData: OverviewPodsUnknownItemBinding.(
+ item: Item,
+ payloads: List
+ ) -> Unit = { item, _ ->
+
+ name.text = item.device.getLabel(context)
+ }
+
+ data class Item(
+ val device: PodDevice
+ ) : OverviewAdapter.Item {
+ override val stableId: Long = this.javaClass.hashCode().toLong()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/monitor/core/MonitorSettings.kt b/app/src/main/java/eu/darken/capod/monitor/core/MonitorSettings.kt
new file mode 100644
index 00000000..14dc0853
--- /dev/null
+++ b/app/src/main/java/eu/darken/capod/monitor/core/MonitorSettings.kt
@@ -0,0 +1,16 @@
+package eu.darken.capod.monitor.core
+
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class MonitorSettings @Inject constructor() {
+
+ val mode: Mode = Mode.ALWAYS
+
+ enum class Mode(val raw: String) {
+ MANUAL("monitor.mode.manual"),
+ AUTOMATIC("monitor.mode.automatic"),
+ ALWAYS("monitor.mode.always")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/monitor/core/receiver/BluetoothEventReceiver.kt b/app/src/main/java/eu/darken/capod/monitor/core/receiver/BluetoothEventReceiver.kt
index 2b041e7d..2d544bc5 100644
--- a/app/src/main/java/eu/darken/capod/monitor/core/receiver/BluetoothEventReceiver.kt
+++ b/app/src/main/java/eu/darken/capod/monitor/core/receiver/BluetoothEventReceiver.kt
@@ -13,7 +13,7 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.monitor.core.worker.MonitorControl
-import eu.darken.capod.pods.core.airpods.ContinuityProtocol
+import eu.darken.capod.pods.core.airpods.protocol.ContinuityProtocol
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
diff --git a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorControl.kt b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorControl.kt
index d243c096..19479ab3 100644
--- a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorControl.kt
+++ b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorControl.kt
@@ -22,7 +22,7 @@ class MonitorControl @Inject constructor(
suspend fun startMonitor(
bluetoothDevice: BluetoothDevice? = null,
- forceStart: Boolean
+ forceStart: Boolean = false,
): Unit = withContext(dispatcherProvider.IO) {
val workerData = Data.Builder().apply {
diff --git a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorker.kt b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorker.kt
index e8e8c6ff..f65ce472 100644
--- a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorker.kt
+++ b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorker.kt
@@ -8,17 +8,21 @@ import androidx.work.WorkerParameters
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.hilt.EntryPoints
-import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
-import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
+import eu.darken.capod.common.debug.Bugs
+import eu.darken.capod.common.debug.logging.Logging.Priority.*
import eu.darken.capod.common.debug.logging.asLog
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.setupCommonEventHandlers
+import eu.darken.capod.common.permissions.Permission
+import eu.darken.capod.common.permissions.isGrantedOrNotRequired
import eu.darken.capod.monitor.core.MonitorComponent
import eu.darken.capod.monitor.core.MonitorCoroutineScope
+import eu.darken.capod.monitor.core.MonitorSettings
import eu.darken.capod.monitor.ui.MonitorNotifications
-import eu.darken.capod.pods.core.airpods.ContinuityProtocol
+import eu.darken.capod.pods.core.airpods.protocol.ContinuityProtocol
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
@@ -30,6 +34,7 @@ class MonitorWorker @AssistedInject constructor(
monitorComponentBuilder: MonitorComponent.Builder,
private val monitorNotifications: MonitorNotifications,
private val notificationManager: NotificationManager,
+ private val monitorSettings: MonitorSettings,
) : CoroutineWorker(context, params) {
private val workerScope = MonitorCoroutineScope()
@@ -53,66 +58,80 @@ class MonitorWorker @AssistedInject constructor(
log(TAG, VERBOSE) { "init(): workerId=$id" }
}
- override suspend fun doWork(): Result = try {
- val start = System.currentTimeMillis()
- log(TAG, VERBOSE) { "Executing $inputData now (runAttemptCount=$runAttemptCount)" }
+ override suspend fun doWork(): Result {
+ try {
+ val start = System.currentTimeMillis()
+ log(TAG, VERBOSE) { "Executing $inputData now (runAttemptCount=$runAttemptCount)" }
- bluetoothManager2
- .isBluetoothEnabled
- .flatMapLatest { bluetoothManager2.connectedDevices() }
- .map { devices ->
- devices.any { device ->
- ContinuityProtocol.BLE_FEATURE_UUIDS.any { feature ->
- device.hasFeature(feature)
+ val missingPermissions = Permission.values().filter { !it.isGrantedOrNotRequired(context) }
+ if (missingPermissions.isNotEmpty()) {
+ log(TAG, WARN) { "Aborting, missing permissions: $missingPermissions" }
+ return Result.success()
+ }
+
+ bluetoothManager2
+ .isBluetoothEnabled
+ .flatMapLatest { bluetoothManager2.connectedDevices() }
+ .map { devices ->
+ devices.any { device ->
+ ContinuityProtocol.BLE_FEATURE_UUIDS.any { feature ->
+ device.hasFeature(feature)
+ }
}
}
- }
- .setupCommonEventHandlers(TAG) { "ConnectedDevices" }
- .flatMapLatest { arePodsConnected ->
- flow {
- if (arePodsConnected) {
- log(TAG) { "Pods are connected, aborting any timeout." }
- } else {
- log(TAG) { "No Pods are connected, canceling worker soon." }
- delay(60 * 1000)
- log(TAG) { "Canceling worker now, still no Pods connected." }
- // FIXME
-// workerScope.coroutineContext.cancelChildren()
+ .setupCommonEventHandlers(TAG) { "ConnectedDevices" }
+ .flatMapLatest { arePodsConnected ->
+ log(TAG) { "Monitor mode: ${monitorSettings.mode}" }
+ when (monitorSettings.mode) {
+ MonitorSettings.Mode.MANUAL -> emptyFlow()
+ MonitorSettings.Mode.ALWAYS -> emptyFlow()
+ MonitorSettings.Mode.AUTOMATIC -> flow {
+ if (arePodsConnected) {
+ log(TAG) { "Pods are connected, aborting any timeout." }
+ } else {
+ log(TAG) { "No Pods are connected, canceling worker soon." }
+ delay(60 * 1000)
+ log(TAG) { "Canceling worker now, still no Pods connected." }
+
+ workerScope.coroutineContext.cancelChildren()
+ }
+ }
}
}
- }
- .launchIn(workerScope)
+ .launchIn(workerScope)
- val monitorJob = podMonitor.pods
- .setupCommonEventHandlers(TAG) { "PodMonitor" }
- .onStart {
- setForeground(monitorNotifications.getForegroundInfo(null))
- }
- .onEach {
- notificationManager.notify(
- MonitorNotifications.NOTIFICATION_ID,
- monitorNotifications.getNotification(it.firstOrNull())
- )
- }
- .launchIn(workerScope)
+ val monitorJob = podMonitor.pods
+ .setupCommonEventHandlers(TAG) { "PodMonitor" }
+ .onStart {
+ setForeground(monitorNotifications.getForegroundInfo(null))
+ }
+ .onEach {
+ notificationManager.notify(
+ MonitorNotifications.NOTIFICATION_ID,
+ monitorNotifications.getNotification(it.firstOrNull())
+ )
+ }
+ .launchIn(workerScope)
- log(TAG, VERBOSE) { "monitor job is active" }
- monitorJob.join()
- log(TAG, VERBOSE) { "monitor job quit" }
+ log(TAG, VERBOSE) { "Monitor job is active" }
+ monitorJob.join()
+ log(TAG, VERBOSE) { "Monitor job quit" }
- val duration = System.currentTimeMillis() - start
+ val duration = System.currentTimeMillis() - start
- log(TAG, VERBOSE) { "Execution finished after ${duration}ms, $inputData" }
+ log(TAG, VERBOSE) { "Execution finished after ${duration}ms, $inputData" }
- Result.success(inputData)
- } catch (e: Throwable) {
- log(TAG, ERROR) { "Execution failed:\n${e.asLog()}" }
- finishedWithError = true
- // TODO update result?
- Result.failure(inputData)
- } finally {
- this.workerScope.cancel("Worker finished (withError?=$finishedWithError).")
+ return Result.success(inputData)
+ } catch (e: Throwable) {
+ log(TAG, ERROR) { "Execution failed:\n${e.asLog()}" }
+ Bugs.report(e)
+ finishedWithError = true
+ // TODO update result?
+ return Result.failure(inputData)
+ } finally {
+ this.workerScope.cancel("Worker finished (withError?=$finishedWithError).")
+ }
}
companion object {
diff --git a/app/src/main/java/eu/darken/capod/pods/core/DualPods.kt b/app/src/main/java/eu/darken/capod/pods/core/DualPods.kt
new file mode 100644
index 00000000..4e3c662f
--- /dev/null
+++ b/app/src/main/java/eu/darken/capod/pods/core/DualPods.kt
@@ -0,0 +1,27 @@
+package eu.darken.capod.pods.core
+
+interface DualPods : PodDevice {
+
+ val microPhonePod: Pod
+
+ enum class Pod {
+ LEFT,
+ RIGHT
+ }
+
+ val batteryLeftPodPercent: Float?
+
+ val batteryRightPodPercent: Float?
+
+ val batteryCasePercent: Float?
+
+ val isLeftPodInEar: Boolean
+
+ val isRightPodInEar: Boolean
+
+ val isCaseCharging: Boolean
+
+ val isLeftPodCharging: Boolean
+
+ val isRightPodCharging: Boolean
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/pods/core/DualPodsExtensions.kt b/app/src/main/java/eu/darken/capod/pods/core/DualPodsExtensions.kt
new file mode 100644
index 00000000..f952ab59
--- /dev/null
+++ b/app/src/main/java/eu/darken/capod/pods/core/DualPodsExtensions.kt
@@ -0,0 +1,17 @@
+package eu.darken.capod.pods.core
+
+import android.content.Context
+import eu.darken.capod.R
+import kotlin.math.roundToInt
+
+fun DualPods.getBatteryLeftPod(context: Context): String =
+ batteryLeftPodPercent?.let { "${(it * 100).roundToInt()}%" }
+ ?: context.getString(R.string.general_value_not_available_label)
+
+fun DualPods.getBatteryRightPod(context: Context): String =
+ batteryRightPodPercent?.let { "${(it * 100).roundToInt()}%" }
+ ?: context.getString(R.string.general_value_not_available_label)
+
+fun DualPods.getBatteryCase(context: Context): String =
+ batteryCasePercent?.let { "${(it * 100).roundToInt()}%" }
+ ?: context.getString(R.string.general_value_not_available_label)
diff --git a/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt
index 5af6663f..61375fc5 100644
--- a/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt
+++ b/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt
@@ -1,10 +1,14 @@
package eu.darken.capod.pods.core
import android.bluetooth.le.ScanResult
+import android.content.Context
interface PodDevice {
val scanResult: ScanResult
- interface Status
+ val rssi: Int
+ get() = scanResult.rssi
+
+ fun getLabel(context: Context): String
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/pods/core/airpods/AirPodsFactory.kt b/app/src/main/java/eu/darken/capod/pods/core/airpods/AirPodsFactory.kt
index 4d2e5113..22649562 100644
--- a/app/src/main/java/eu/darken/capod/pods/core/airpods/AirPodsFactory.kt
+++ b/app/src/main/java/eu/darken/capod/pods/core/airpods/AirPodsFactory.kt
@@ -2,7 +2,7 @@ package eu.darken.capod.pods.core.airpods
import android.bluetooth.le.ScanResult
import dagger.Reusable
-import eu.darken.capod.common.debug.bugreporting.Bugs
+import eu.darken.capod.common.debug.Bugs
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
import eu.darken.capod.common.debug.logging.asLog
@@ -13,6 +13,8 @@ import eu.darken.capod.pods.core.airpods.models.AirPodsGen1
import eu.darken.capod.pods.core.airpods.models.AirPodsGen2
import eu.darken.capod.pods.core.airpods.models.AirPodsPro
import eu.darken.capod.pods.core.airpods.models.UnknownAppleDevice
+import eu.darken.capod.pods.core.airpods.protocol.ContinuityProtocol
+import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
import javax.inject.Inject
@Reusable
diff --git a/app/src/main/java/eu/darken/capod/pods/core/airpods/ApplePods.kt b/app/src/main/java/eu/darken/capod/pods/core/airpods/ApplePods.kt
index 347a6fbc..33df1d5a 100644
--- a/app/src/main/java/eu/darken/capod/pods/core/airpods/ApplePods.kt
+++ b/app/src/main/java/eu/darken/capod/pods/core/airpods/ApplePods.kt
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.airpods
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
+import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
interface ApplePods : PodDevice {
@@ -9,6 +10,6 @@ interface ApplePods : PodDevice {
companion object {
- val TAG = logTag("Pod", "BaseAirPods")
+ val TAG = logTag("Pod", "AppleDevice")
}
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/pods/core/airpods/DualApplePods.kt b/app/src/main/java/eu/darken/capod/pods/core/airpods/DualApplePods.kt
new file mode 100644
index 00000000..bb7704d6
--- /dev/null
+++ b/app/src/main/java/eu/darken/capod/pods/core/airpods/DualApplePods.kt
@@ -0,0 +1,197 @@
+package eu.darken.capod.pods.core.airpods
+
+import android.content.Context
+import androidx.annotation.StringRes
+import eu.darken.capod.R
+import eu.darken.capod.common.debug.logging.log
+import eu.darken.capod.common.isBitSet
+import eu.darken.capod.common.lowerNibble
+import eu.darken.capod.common.upperNibble
+import eu.darken.capod.pods.core.DualPods
+import eu.darken.capod.pods.core.DualPods.Pod
+
+interface DualApplePods : ApplePods, DualPods {
+
+ val tag: String
+
+ // We start counting at the airpods prefix byte
+ val rawPrefix: UByte
+ get() = proximityMessage.data[0]
+
+ val rawDeviceModel: UShort
+ get() = (((proximityMessage.data[1].toInt() and 255) shl 8) or (proximityMessage.data[2].toInt() and 255)).toUShort()
+
+ val rawStatus: UByte
+ get() = proximityMessage.data[3]
+
+ val rawPodsBattery: UByte
+ get() = proximityMessage.data[4]
+
+ val rawCaseBattery: UByte
+ get() = proximityMessage.data[5]
+
+ val rawCaseLidState: UByte
+ get() = proximityMessage.data[6]
+
+ val rawDeviceColor: UByte
+ get() = proximityMessage.data[7]
+
+ val rawSuffix: UByte
+ get() = proximityMessage.data[8]
+
+ override val microPhonePod: Pod
+ get() = when (rawStatus.isBitSet(5)) {
+ true -> Pod.LEFT
+ false -> Pod.RIGHT
+ }
+
+ override val batteryLeftPodPercent: Float?
+ get() {
+ val value = when (microPhonePod) {
+ Pod.LEFT -> rawPodsBattery.lowerNibble.toInt()
+ Pod.RIGHT -> rawPodsBattery.upperNibble.toInt()
+ }
+ return when (value) {
+ 15 -> null
+ else -> if (value > 10) {
+ log(tag) { "Left pod: Above 100% battery: $value" }
+ 1.0f
+ } else {
+ (value / 10f)
+ }
+ }
+ }
+
+ override val batteryRightPodPercent: Float?
+ get() {
+ val value = when (microPhonePod) {
+ Pod.LEFT -> rawPodsBattery.upperNibble.toInt()
+ Pod.RIGHT -> rawPodsBattery.lowerNibble.toInt()
+ }
+ return when (value) {
+ 15 -> null
+ else -> if (value > 10) {
+ log(tag) { "Right pod: Above 100% battery: $value" }
+ 1.0f
+ } else {
+ value / 10f
+ }
+ }
+ }
+
+ override val batteryCasePercent: Float?
+ get() = when (val value = rawCaseBattery.lowerNibble.toInt()) {
+ 15 -> null
+ else -> if (value > 10) {
+ log(tag) { "Case: Above 100% battery: $value" }
+ 1.0f
+ } else {
+ value / 10f
+ }
+ }
+
+ override val isLeftPodInEar: Boolean
+ get() = when (microPhonePod) {
+ Pod.LEFT -> rawStatus.isBitSet(1)
+ Pod.RIGHT -> rawStatus.isBitSet(3)
+ }
+
+ override val isRightPodInEar: Boolean
+ get() = when (microPhonePod) {
+ Pod.LEFT -> rawStatus.isBitSet(3)
+ Pod.RIGHT -> rawStatus.isBitSet(1)
+ }
+
+ val status: Status
+ get() = Status.values().firstOrNull { it.raw == rawStatus } ?: Status.UNKNOWN
+
+ enum class Status(val raw: UByte?) {
+ BOTH_AIRPODS_IN_CASE(0x55),
+ LEFT_IN_EAR(0x33),
+ AIRPLANE(0x02),
+ UNKNOWN(null);
+
+ constructor(raw: Int) : this(raw.toUByte())
+ }
+
+ // 1010101 0x55 85 Both In Case
+ // 0101011 0x2b 43 Both In Ear
+ // 0101001 0x29 41 Right in Ear
+ // 0100011 0x23 35 Left In Ear
+ // 0100001 0x21 33 Neither in Ear or In Case
+ // 1110001 0x71 113 Left in Case, Right on Desk
+ // 0010001 0x11 17 Left in Case, Right on Desk
+ // 0010011 0x13 19 Left in Case, Right in Ear
+ // 1110011 0x73 115 Left in Case, Right in Ear
+
+
+ override val isCaseCharging: Boolean
+ get() = rawCaseBattery.upperNibble.isBitSet(2)
+
+ override val isLeftPodCharging: Boolean
+ get() = when (microPhonePod) {
+ Pod.LEFT -> rawCaseBattery.upperNibble.isBitSet(0)
+ Pod.RIGHT -> rawCaseBattery.upperNibble.isBitSet(1)
+ }
+
+ override val isRightPodCharging: Boolean
+ get() = when (microPhonePod) {
+ Pod.LEFT -> rawCaseBattery.upperNibble.isBitSet(1)
+ Pod.RIGHT -> rawCaseBattery.upperNibble.isBitSet(0)
+ }
+
+ val caseLidState: LidState
+ get() = LidState.values().firstOrNull { it.raw == rawCaseLidState } ?: LidState.UNKNOWN
+
+ enum class LidState(val raw: UByte?) {
+ OPEN(0x31),
+ CLOSED(0x38),
+ NOT_IN_CASE(0x01),
+ UNKNOWN(null);
+
+ constructor(raw: Int) : this(raw.toUByte())
+ }
+
+ val deviceColor: DeviceColor
+ get() = DeviceColor.values().firstOrNull { it.raw == rawDeviceColor } ?: DeviceColor.UNKNOWN
+
+
+ fun getDeviceColorLabel(context: Context): String = context.getString(deviceColor.labelRes)
+
+ enum class DeviceColor(val raw: UByte?, @StringRes val labelRes: Int) {
+
+ WHITE(0x00, R.string.pods_device_color_white_label),
+ BLACK(0x01, R.string.pods_device_color_black_label),
+ RED(0x02, R.string.pods_device_color_red_label),
+ BLUE(0x03, R.string.pods_device_color_blue_label),
+ PINK(0x04, R.string.pods_device_color_pink_label),
+ GRAY(0x05, R.string.pods_device_color_gray_label),
+ SILVER(0x06, R.string.pods_device_color_silver_label),
+ GOLD(0x07, R.string.pods_device_color_gold_label),
+ ROSE_GOLD(0x08, R.string.pods_device_color_rose_gold_label),
+ SPACE_GRAY(0x09, R.string.pods_device_color_space_gray_label),
+ DARK_BLUE(0x0a, R.string.pods_device_color_dark_blue_label),
+ LIGHT_BLUE(0x0b, R.string.pods_device_color_light_blue_label),
+ YELLOW(0x0c, R.string.pods_device_color_yellow_label),
+ UNKNOWN(null, R.string.general_value_unknown_label);
+
+ constructor(raw: Int, @StringRes labelRes: Int) : this(raw.toUByte(), labelRes)
+ }
+
+ val connectionState: ConnectionState
+ 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) {
+ DISCONNECTED(0x00, R.string.pods_connection_state_disconnected_label),
+ IDLE(0x04, R.string.pods_connection_state_idle_label),
+ MUSIC(0x05, R.string.pods_connection_state_music_label),
+ CALL(0x06, R.string.pods_connection_state_call_label),
+ RINGING(0x07, R.string.pods_connection_state_ringing_label),
+ HANGING_UP(0x09, R.string.pods_connection_state_hanging_up_label),
+ UNKNOWN(null, R.string.general_value_unknown_label);
+
+ constructor(raw: Int, @StringRes labelRes: Int) : this(raw.toUByte(), labelRes)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/pods/core/airpods/AirPodsDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/airpods/SingleApplePods.kt
similarity index 89%
rename from app/src/main/java/eu/darken/capod/pods/core/airpods/AirPodsDevice.kt
rename to app/src/main/java/eu/darken/capod/pods/core/airpods/SingleApplePods.kt
index 68c0785f..7aacec38 100644
--- a/app/src/main/java/eu/darken/capod/pods/core/airpods/AirPodsDevice.kt
+++ b/app/src/main/java/eu/darken/capod/pods/core/airpods/SingleApplePods.kt
@@ -4,8 +4,10 @@ import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.isBitSet
import eu.darken.capod.common.lowerNibble
import eu.darken.capod.common.upperNibble
+import eu.darken.capod.pods.core.DualPods
+import eu.darken.capod.pods.core.DualPods.Pod
-interface AirPodsDevice : ApplePods {
+interface SingleApplePods : ApplePods, DualPods {
val tag: String
@@ -34,18 +36,13 @@ interface AirPodsDevice : ApplePods {
val rawSuffix: UByte
get() = proximityMessage.data[8]
- val microPhonePod: Pod
+ override val microPhonePod: Pod
get() = when (rawStatus.isBitSet(5)) {
true -> Pod.LEFT
false -> Pod.RIGHT
}
- enum class Pod {
- LEFT,
- RIGHT
- }
-
- val batteryLeftPodPercent: Float?
+ override val batteryLeftPodPercent: Float?
get() {
val value = when (microPhonePod) {
Pod.LEFT -> rawPodsBattery.lowerNibble.toInt()
@@ -53,7 +50,7 @@ interface AirPodsDevice : ApplePods {
}
return when (value) {
15 -> null
- else -> if (value >= 10) {
+ else -> if (value > 10) {
log(tag) { "Left pod: Above 100% battery: $value" }
1.0f
} else {
@@ -62,7 +59,7 @@ interface AirPodsDevice : ApplePods {
}
}
- val batteryRightPodPercent: Float?
+ override val batteryRightPodPercent: Float?
get() {
val value = when (microPhonePod) {
Pod.LEFT -> rawPodsBattery.upperNibble.toInt()
@@ -79,7 +76,7 @@ interface AirPodsDevice : ApplePods {
}
}
- val batteryCasePercent: Float?
+ override val batteryCasePercent: Float?
get() = when (val value = rawCaseBattery.lowerNibble.toInt()) {
15 -> null
else -> if (value > 10) {
@@ -90,13 +87,13 @@ interface AirPodsDevice : ApplePods {
}
}
- val isLeftPodInEar: Boolean
+ override val isLeftPodInEar: Boolean
get() = when (microPhonePod) {
Pod.LEFT -> rawStatus.isBitSet(1)
Pod.RIGHT -> rawStatus.isBitSet(3)
}
- val isRightPodInEar: Boolean
+ override val isRightPodInEar: Boolean
get() = when (microPhonePod) {
Pod.LEFT -> rawStatus.isBitSet(3)
Pod.RIGHT -> rawStatus.isBitSet(1)
@@ -125,16 +122,16 @@ interface AirPodsDevice : ApplePods {
// 1110011 0x73 115 Left in Case, Right in Ear
- val isCaseCharging: Boolean
+ override val isCaseCharging: Boolean
get() = rawCaseBattery.upperNibble.isBitSet(2)
- val isLeftPodCharging: Boolean
+ override val isLeftPodCharging: Boolean
get() = when (microPhonePod) {
Pod.LEFT -> rawCaseBattery.upperNibble.isBitSet(0)
Pod.RIGHT -> rawCaseBattery.upperNibble.isBitSet(1)
}
- val isRightPodCharging: Boolean
+ override val isRightPodCharging: Boolean
get() = when (microPhonePod) {
Pod.LEFT -> rawCaseBattery.upperNibble.isBitSet(1)
Pod.RIGHT -> rawCaseBattery.upperNibble.isBitSet(0)
diff --git a/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen1.kt b/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen1.kt
index 504ebfec..698fe8c2 100644
--- a/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen1.kt
+++ b/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen1.kt
@@ -1,13 +1,19 @@
package eu.darken.capod.pods.core.airpods.models
import android.bluetooth.le.ScanResult
+import android.content.Context
import eu.darken.capod.common.debug.logging.logTag
-import eu.darken.capod.pods.core.airpods.AirPodsDevice
-import eu.darken.capod.pods.core.airpods.ProximityPairing
+import eu.darken.capod.pods.core.airpods.DualApplePods
+import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
data class AirPodsGen1 constructor(
override val scanResult: ScanResult,
override val proximityMessage: ProximityPairing.Message
-) : AirPodsDevice {
+) : DualApplePods {
+
+ override fun getLabel(context: Context): String {
+ return "AirPods (Gen 1)"
+ }
+
override val tag: String = logTag("Pod", "AirPodsGen1")
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen2.kt b/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen2.kt
index a2a487fa..d1024a17 100644
--- a/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen2.kt
+++ b/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen2.kt
@@ -1,13 +1,19 @@
package eu.darken.capod.pods.core.airpods.models
import android.bluetooth.le.ScanResult
+import android.content.Context
import eu.darken.capod.common.debug.logging.logTag
-import eu.darken.capod.pods.core.airpods.AirPodsDevice
-import eu.darken.capod.pods.core.airpods.ProximityPairing
+import eu.darken.capod.pods.core.airpods.DualApplePods
+import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
data class AirPodsGen2 constructor(
override val scanResult: ScanResult,
override val proximityMessage: ProximityPairing.Message
-) : AirPodsDevice {
+) : DualApplePods {
+
+ override fun getLabel(context: Context): String {
+ return "AirPods (Gen 2)"
+ }
+
override val tag: String = logTag("Pod", "AirPodsGen2")
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsMax.kt b/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsMax.kt
index 409609af..61a48647 100644
--- a/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsMax.kt
+++ b/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsMax.kt
@@ -1,10 +1,17 @@
package eu.darken.capod.pods.core.airpods.models
import android.bluetooth.le.ScanResult
+import android.content.Context
import eu.darken.capod.pods.core.airpods.ApplePods
-import eu.darken.capod.pods.core.airpods.ProximityPairing
+import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
data class AirPodsMax constructor(
override val scanResult: ScanResult,
override val proximityMessage: ProximityPairing.Message
-) : ApplePods
\ No newline at end of file
+) : ApplePods {
+
+ override fun getLabel(context: Context): String {
+ return "AirPods Max"
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsPro.kt b/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsPro.kt
index 46e5b6c0..2b9c1ec7 100644
--- a/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsPro.kt
+++ b/app/src/main/java/eu/darken/capod/pods/core/airpods/models/AirPodsPro.kt
@@ -1,14 +1,19 @@
package eu.darken.capod.pods.core.airpods.models
import android.bluetooth.le.ScanResult
+import android.content.Context
import eu.darken.capod.common.debug.logging.logTag
-import eu.darken.capod.pods.core.airpods.AirPodsDevice
-import eu.darken.capod.pods.core.airpods.ProximityPairing
+import eu.darken.capod.pods.core.airpods.DualApplePods
+import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
data class AirPodsPro constructor(
override val scanResult: ScanResult,
override val proximityMessage: ProximityPairing.Message
-) : AirPodsDevice {
+) : DualApplePods {
+
+ override fun getLabel(context: Context): String {
+ return "AirPods Pro"
+ }
override val tag: String = logTag("Pod", "AirPodsPro")
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/pods/core/airpods/models/UnknownAppleDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/airpods/models/UnknownAppleDevice.kt
index 06cc3f11..cd3e6724 100644
--- a/app/src/main/java/eu/darken/capod/pods/core/airpods/models/UnknownAppleDevice.kt
+++ b/app/src/main/java/eu/darken/capod/pods/core/airpods/models/UnknownAppleDevice.kt
@@ -1,10 +1,17 @@
package eu.darken.capod.pods.core.airpods.models
import android.bluetooth.le.ScanResult
+import android.content.Context
import eu.darken.capod.pods.core.airpods.ApplePods
-import eu.darken.capod.pods.core.airpods.ProximityPairing
+import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
data class UnknownAppleDevice constructor(
override val scanResult: ScanResult,
override val proximityMessage: ProximityPairing.Message
-) : ApplePods
\ No newline at end of file
+) : ApplePods {
+
+ override fun getLabel(context: Context): String {
+ return "Unknown device"
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/darken/capod/pods/core/airpods/ContinuityProtocol.kt b/app/src/main/java/eu/darken/capod/pods/core/airpods/protocol/ContinuityProtocol.kt
similarity index 97%
rename from app/src/main/java/eu/darken/capod/pods/core/airpods/ContinuityProtocol.kt
rename to app/src/main/java/eu/darken/capod/pods/core/airpods/protocol/ContinuityProtocol.kt
index a3151aec..40167022 100644
--- a/app/src/main/java/eu/darken/capod/pods/core/airpods/ContinuityProtocol.kt
+++ b/app/src/main/java/eu/darken/capod/pods/core/airpods/protocol/ContinuityProtocol.kt
@@ -1,4 +1,4 @@
-package eu.darken.capod.pods.core.airpods
+package eu.darken.capod.pods.core.airpods.protocol
import android.bluetooth.le.ScanResult
import android.os.ParcelUuid
diff --git a/app/src/main/java/eu/darken/capod/pods/core/airpods/ProximityPairing.kt b/app/src/main/java/eu/darken/capod/pods/core/airpods/protocol/ProximityPairing.kt
similarity index 94%
rename from app/src/main/java/eu/darken/capod/pods/core/airpods/ProximityPairing.kt
rename to app/src/main/java/eu/darken/capod/pods/core/airpods/protocol/ProximityPairing.kt
index c944f055..bd7e5841 100644
--- a/app/src/main/java/eu/darken/capod/pods/core/airpods/ProximityPairing.kt
+++ b/app/src/main/java/eu/darken/capod/pods/core/airpods/protocol/ProximityPairing.kt
@@ -1,8 +1,9 @@
-package eu.darken.capod.pods.core.airpods
+package eu.darken.capod.pods.core.airpods.protocol
import android.bluetooth.le.ScanFilter
import dagger.Reusable
import eu.darken.capod.common.debug.logging.log
+import eu.darken.capod.pods.core.airpods.ApplePods
import javax.inject.Inject
diff --git a/app/src/main/res/drawable/ic_baseline_apps_24.xml b/app/src/main/res/drawable/ic_baseline_apps_24.xml
new file mode 100644
index 00000000..f814c66e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_apps_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_earbuds_24.xml b/app/src/main/res/drawable/ic_baseline_earbuds_24.xml
new file mode 100644
index 00000000..8d729ff2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_earbuds_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_pod_case_24.xml b/app/src/main/res/drawable/ic_baseline_pod_case_24.xml
new file mode 100644
index 00000000..35655ef7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_pod_case_24.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_pod_left_24.xml b/app/src/main/res/drawable/ic_baseline_pod_left_24.xml
new file mode 100644
index 00000000..6b2642ed
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_pod_left_24.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_pod_right_24.xml b/app/src/main/res/drawable/ic_baseline_pod_right_24.xml
new file mode 100644
index 00000000..48c1a736
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_pod_right_24.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_signal_cellular_alt_24.xml b/app/src/main/res/drawable/ic_baseline_signal_cellular_alt_24.xml
new file mode 100644
index 00000000..f80d2d37
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_signal_cellular_alt_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml
index b3775026..b1294687 100644
--- a/app/src/main/res/layout/main_activity.xml
+++ b/app/src/main/res/layout/main_activity.xml
@@ -9,7 +9,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/overview_pods_unknown_item.xml b/app/src/main/res/layout/overview_pods_unknown_item.xml
new file mode 100644
index 00000000..b312b2f0
--- /dev/null
+++ b/app/src/main/res/layout/overview_pods_unknown_item.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml
index 6d72adaa..a09bb8cb 100644
--- a/app/src/main/res/navigation/nav_graph.xml
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -7,7 +7,7 @@
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 225b8535..291dbc0e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -16,4 +16,37 @@
Recorded log file
Record debug log
Settings
+ BLUETOOTH
+ Allows applications to connect to paired bluetooth devices.
+ Allows an app to access precise location.
+ ACCESS_FINE_LOCATION
+ Left pod
+ Right pod
+ Case
+ Open
+ Closed
+ N/A
+ Raw data
+ Unknown
+ Reception %s
+ White
+ Yellow
+ Light blue
+ Dark blue
+ Space gray
+ Rose gold
+ Gold
+ Silver
+ Gray
+ Pink
+ Blue
+ Red
+ Black
+ Disconnected
+ Idle
+ Music
+ Call
+ Ringing
+ Hanging up
+ Status %s
\ No newline at end of file
diff --git a/app/src/test/java/eu/darken/capod/pods/core/airpods/AirPodsFactoryTest.kt b/app/src/test/java/eu/darken/capod/pods/core/airpods/AirPodsFactoryTest.kt
index 43917c79..25dd8e8a 100644
--- a/app/src/test/java/eu/darken/capod/pods/core/airpods/AirPodsFactoryTest.kt
+++ b/app/src/test/java/eu/darken/capod/pods/core/airpods/AirPodsFactoryTest.kt
@@ -3,10 +3,13 @@ package eu.darken.capod.pods.core.airpods
import android.bluetooth.BluetoothDevice
import android.bluetooth.le.ScanRecord
import android.bluetooth.le.ScanResult
+import eu.darken.capod.pods.core.DualPods
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.airpods.models.AirPodsGen1
import eu.darken.capod.pods.core.airpods.models.AirPodsPro
import eu.darken.capod.pods.core.airpods.models.UnknownAppleDevice
+import eu.darken.capod.pods.core.airpods.protocol.ContinuityProtocol
+import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.instanceOf
import io.mockk.MockKAnnotations
@@ -65,39 +68,39 @@ class AirPodsFactoryTest : BaseTest() {
@Test
fun `test AirPodDevice - active microphone`() = runBlockingTest {
- create("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("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
// --^-----
- microPhonePod shouldBe AirPodsDevice.Pod.LEFT
+ microPhonePod shouldBe DualPods.Pod.LEFT
}
- create("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("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
// --^-----
- microPhonePod shouldBe AirPodsDevice.Pod.RIGHT
+ microPhonePod shouldBe DualPods.Pod.RIGHT
}
}
@Test
fun `test AirPodDevice - left pod ear status`() = runBlockingTest {
// Left Pod primary
- create("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("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
// 765432¹0
isLeftPodInEar shouldBe true
}
- create("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("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
// 765432¹0
isLeftPodInEar shouldBe false
}
// Right Pod is primary
- create("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("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
// 7654³210
isLeftPodInEar shouldBe true
}
- create("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("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
// 7654³210
isLeftPodInEar shouldBe false
@@ -107,24 +110,24 @@ class AirPodsFactoryTest : BaseTest() {
@Test
fun `test AirPodDevice - right pod ear status`() = runBlockingTest {
// Left Pod primary
- create("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("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
// 7654³210
isRightPodInEar shouldBe true
}
- create("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("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
// 7654³210
isRightPodInEar shouldBe false
}
// Right Pod is primary
- create("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("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
// 765432¹0
isRightPodInEar shouldBe true
}
- create("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("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
// 765432¹0
isRightPodInEar shouldBe false
@@ -134,13 +137,13 @@ class AirPodsFactoryTest : BaseTest() {
@Test
fun `test AirPodDevice - battery status`() = runBlockingTest {
// Right Pod is primary
- create("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("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
batteryLeftPodPercent shouldBe 0.9f
batteryRightPodPercent shouldBe 0.8f
}
// Left Pod primary
- create("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("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
batteryLeftPodPercent shouldBe 0.9f
batteryRightPodPercent shouldBe 0.8f
@@ -153,13 +156,13 @@ class AirPodsFactoryTest : BaseTest() {
* Right pod is charging
*/
// This is the left
- create("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("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
isLeftPodCharging shouldBe false
isRightPodCharging shouldBe true
}
// This is the right
- create("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("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
isLeftPodCharging shouldBe false
isRightPodCharging shouldBe true
@@ -169,26 +172,26 @@ class AirPodsFactoryTest : BaseTest() {
* Left pod is charging
*/
// This is the left
- create("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("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
isLeftPodCharging shouldBe true
isRightPodCharging shouldBe false
}
// This is the right
- create("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("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
isLeftPodCharging shouldBe true
isRightPodCharging shouldBe false
}
// Both charging
- create("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("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
isLeftPodCharging shouldBe true
isRightPodCharging shouldBe true
}
// Both not charging
- create("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("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
isLeftPodCharging shouldBe false
isRightPodCharging shouldBe false
@@ -197,11 +200,11 @@ class AirPodsFactoryTest : BaseTest() {
@Test
fun `test AirPodDevice - case charging`() = runBlockingTest {
- create("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("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
isCaseCharging shouldBe false
}
- create("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("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
isCaseCharging shouldBe true
}
@@ -210,66 +213,66 @@ class AirPodsFactoryTest : BaseTest() {
@Test
fun `test AirPodDevice - case lid test`() = runBlockingTest {
// Lid open
- create("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("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
- caseLidState shouldBe AirPodsDevice.LidState.OPEN
+ caseLidState shouldBe DualApplePods.LidState.OPEN
}
// Lid just closed
- create("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("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
- caseLidState shouldBe AirPodsDevice.LidState.UNKNOWN
+ caseLidState shouldBe DualApplePods.LidState.UNKNOWN
}
// Lid closed
- create("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("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
- caseLidState shouldBe AirPodsDevice.LidState.CLOSED
+ caseLidState shouldBe DualApplePods.LidState.CLOSED
}
}
@Test
fun `test AirPodDevice - connection state`() = runBlockingTest {
// Disconnected
- create("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("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
- connectionState shouldBe AirPodsDevice.ConnectionState.DISCONNECTED
+ connectionState shouldBe DualApplePods.ConnectionState.DISCONNECTED
}
// Connected idle
- create("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("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
- connectionState shouldBe AirPodsDevice.ConnectionState.IDLE
+ connectionState shouldBe DualApplePods.ConnectionState.IDLE
}
// Connected and playing music
- create("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("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
- connectionState shouldBe AirPodsDevice.ConnectionState.MUSIC
+ connectionState shouldBe DualApplePods.ConnectionState.MUSIC
}
// Connected and call active
- create("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("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
- connectionState shouldBe AirPodsDevice.ConnectionState.CALL
+ connectionState shouldBe DualApplePods.ConnectionState.CALL
}
// Connected and call active
- create("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("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
- connectionState shouldBe AirPodsDevice.ConnectionState.RINGING
+ connectionState shouldBe DualApplePods.ConnectionState.RINGING
}
// Switching?
- create("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("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
- connectionState shouldBe AirPodsDevice.ConnectionState.HANGING_UP
+ connectionState shouldBe DualApplePods.ConnectionState.HANGING_UP
}
}
@Test
fun `create AirPodsGen1`() = runBlockingTest {
- create("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("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()
}
}
@Test
fun `create AirPodsPro`() = runBlockingTest {
- create("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("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()
}
}
diff --git a/app/src/test/java/eu/darken/capod/pods/core/airpods/BaseAirPodsTest.kt b/app/src/test/java/eu/darken/capod/pods/core/airpods/BaseAirPodsTest.kt
index 88cecc78..0bc8ac0d 100644
--- a/app/src/test/java/eu/darken/capod/pods/core/airpods/BaseAirPodsTest.kt
+++ b/app/src/test/java/eu/darken/capod/pods/core/airpods/BaseAirPodsTest.kt
@@ -4,6 +4,8 @@ import android.bluetooth.BluetoothDevice
import android.bluetooth.le.ScanRecord
import android.bluetooth.le.ScanResult
import eu.darken.capod.pods.core.PodDevice
+import eu.darken.capod.pods.core.airpods.protocol.ContinuityProtocol
+import eu.darken.capod.pods.core.airpods.protocol.ProximityPairing
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
diff --git a/app/src/test/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen1Test.kt b/app/src/test/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen1Test.kt
index b8e1f98e..f505e71b 100644
--- a/app/src/test/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen1Test.kt
+++ b/app/src/test/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen1Test.kt
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.airpods.models
-import eu.darken.capod.pods.core.airpods.AirPodsDevice
import eu.darken.capod.pods.core.airpods.BaseAirPodsTest
+import eu.darken.capod.pods.core.airpods.DualApplePods
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.Test
@@ -32,11 +32,11 @@ class AirPodsGen1Test : BaseAirPodsTest() {
isRightPodInEar shouldBe false
batteryCasePercent shouldBe 0.6f
- caseLidState shouldBe AirPodsDevice.LidState.OPEN
+ caseLidState shouldBe DualApplePods.LidState.OPEN
- connectionState shouldBe AirPodsDevice.ConnectionState.DISCONNECTED
+ connectionState shouldBe DualApplePods.ConnectionState.DISCONNECTED
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
}
}
\ No newline at end of file
diff --git a/app/src/test/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen2Test.kt b/app/src/test/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen2Test.kt
index e632db4d..7c32a749 100644
--- a/app/src/test/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen2Test.kt
+++ b/app/src/test/java/eu/darken/capod/pods/core/airpods/models/AirPodsGen2Test.kt
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.airpods.models
-import eu.darken.capod.pods.core.airpods.AirPodsDevice
import eu.darken.capod.pods.core.airpods.BaseAirPodsTest
+import eu.darken.capod.pods.core.airpods.DualApplePods
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.Test
@@ -31,11 +31,11 @@ class AirPodsGen2Test : BaseAirPodsTest() {
isRightPodInEar shouldBe true
batteryCasePercent shouldBe null
- caseLidState shouldBe AirPodsDevice.LidState.NOT_IN_CASE
+ caseLidState shouldBe DualApplePods.LidState.NOT_IN_CASE
- connectionState shouldBe AirPodsDevice.ConnectionState.MUSIC
+ connectionState shouldBe DualApplePods.ConnectionState.MUSIC
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
}
}
\ No newline at end of file
diff --git a/app/src/test/java/eu/darken/capod/pods/core/airpods/models/AirPodsProTest.kt b/app/src/test/java/eu/darken/capod/pods/core/airpods/models/AirPodsProTest.kt
index 735b8ed5..1a93d449 100644
--- a/app/src/test/java/eu/darken/capod/pods/core/airpods/models/AirPodsProTest.kt
+++ b/app/src/test/java/eu/darken/capod/pods/core/airpods/models/AirPodsProTest.kt
@@ -1,7 +1,8 @@
package eu.darken.capod.pods.core.airpods.models
-import eu.darken.capod.pods.core.airpods.AirPodsDevice
+import eu.darken.capod.pods.core.DualPods
import eu.darken.capod.pods.core.airpods.BaseAirPodsTest
+import eu.darken.capod.pods.core.airpods.DualApplePods
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.Test
@@ -21,7 +22,7 @@ class AirPodsProTest : BaseAirPodsTest() {
rawDeviceColor shouldBe 0x00.toUByte()
rawSuffix shouldBe 0x00.toUByte()
- microPhonePod shouldBe AirPodsDevice.Pod.RIGHT
+ microPhonePod shouldBe DualPods.Pod.RIGHT
batteryLeftPodPercent shouldBe 1.0f
batteryRightPodPercent shouldBe 1.0f
@@ -31,14 +32,14 @@ class AirPodsProTest : BaseAirPodsTest() {
isLeftPodCharging shouldBe true
batteryCasePercent shouldBe 0.5f
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
}
@Test
fun `test AirPods from my downstairs neighbour`() = runBlockingTest {
create("07 19 01 0E 20 00 F3 8F 02 00 04 79 C6 3F F9 C3 15 D9 11 A1 3C B1 58 66 B9 8B 67") {
- microPhonePod shouldBe AirPodsDevice.Pod.RIGHT
+ microPhonePod shouldBe DualPods.Pod.RIGHT
batteryLeftPodPercent shouldBe null
batteryRightPodPercent shouldBe 0.3f
@@ -48,7 +49,7 @@ class AirPodsProTest : BaseAirPodsTest() {
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe null
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
}
@@ -64,7 +65,7 @@ class AirPodsProTest : BaseAirPodsTest() {
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe null
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
create("0719010e202b668f01000500000000000000000000000000000000") {
@@ -76,7 +77,7 @@ class AirPodsProTest : BaseAirPodsTest() {
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe null
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
create("0719010e202b668f01000400000000000000000000000000000000") {
@@ -88,7 +89,7 @@ class AirPodsProTest : BaseAirPodsTest() {
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe null
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
create("0719010e200b668f01000500000000000000000000000000000000") {
@@ -100,7 +101,7 @@ class AirPodsProTest : BaseAirPodsTest() {
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe null
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
create("0719010e2003668f01000500000000000000000000000000000000") {
@@ -112,7 +113,7 @@ class AirPodsProTest : BaseAirPodsTest() {
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe null
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
create("0719010e2001668f01000500000000000000000000000000000000") {
@@ -124,7 +125,7 @@ class AirPodsProTest : BaseAirPodsTest() {
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe null
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
create("0719010e2009668f01000500000000000000000000000000000000") {
@@ -136,7 +137,7 @@ class AirPodsProTest : BaseAirPodsTest() {
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe null
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
create("0719010e2053669653000500000000000000000000000000000000") {
@@ -148,7 +149,7 @@ class AirPodsProTest : BaseAirPodsTest() {
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe 0.6f
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
create("0719010e203366a602000500000000000000000000000000000000") {
@@ -160,7 +161,7 @@ class AirPodsProTest : BaseAirPodsTest() {
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe 0.6f
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
create("0719010e202b768f02000500000000000000000000000000000000") {
@@ -172,7 +173,7 @@ class AirPodsProTest : BaseAirPodsTest() {
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe null
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
}
@@ -188,7 +189,7 @@ class AirPodsProTest : BaseAirPodsTest() {
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe 0.6f
- deviceColor shouldBe AirPodsDevice.DeviceColor.WHITE
+ deviceColor shouldBe DualApplePods.DeviceColor.WHITE
}
}
}
\ No newline at end of file