mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Compare commits
16
Commits
v2.7.2-rc0
...
v2.8.0-rc0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18dcf603e9 | ||
|
|
71aaba1b86 | ||
|
|
64b9d5f17a | ||
|
|
a9f23c4bfe | ||
|
|
f454f46778 | ||
|
|
432936ea2c | ||
|
|
8211b0ac88 | ||
|
|
c314987e13 | ||
|
|
fc6cd74752 | ||
|
|
a47011e579 | ||
|
|
fbeeec6539 | ||
|
|
e1cd79cd9d | ||
|
|
692ced1ee7 | ||
|
|
c5cc127df4 | ||
|
|
5f00f10ae8 | ||
|
|
0fc9716f06 |
@@ -21,6 +21,10 @@ android {
|
|||||||
buildConfigField("String", "APPLICATION_ID", "\"${ProjectConfig.packageName}\"")
|
buildConfigField("String", "APPLICATION_ID", "\"${ProjectConfig.packageName}\"")
|
||||||
buildConfigField("String", "GITSHA", "\"${lastCommitHash()}\"")
|
buildConfigField("String", "GITSHA", "\"${lastCommitHash()}\"")
|
||||||
buildConfigField("String", "BUILDTIME", "\"${buildTime()}\"")
|
buildConfigField("String", "BUILDTIME", "\"${buildTime()}\"")
|
||||||
|
|
||||||
|
manifestPlaceholders["bugsnagApiKey"] = getBugSnagApiKey(
|
||||||
|
File(System.getProperty("user.home"), ".appconfig/${ProjectConfig.packageName}/bugsnag.properties")
|
||||||
|
) ?: "bugsnag_apikey_placeholder"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
@@ -95,6 +99,7 @@ dependencies {
|
|||||||
|
|
||||||
addTesting()
|
addTesting()
|
||||||
|
|
||||||
implementation("com.bugsnag:bugsnag-android:5.9.2")
|
"gplayImplementation"("com.bugsnag:bugsnag-android:5.9.2")
|
||||||
|
"gplayImplementation"("com.getkeepsafe.relinker:relinker:1.4.3")
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package eu.darken.capod.debug.autoreport
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
|
||||||
|
import eu.darken.capod.debug.autoreport.FossAutoReporting
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
@Module
|
||||||
|
abstract class AutoReportingModule {
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun autoreporting(foss: FossAutoReporting): AutomaticBugReporter
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package eu.darken.capod.debug.autoreport
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class FossAutoReporting @Inject constructor() : AutomaticBugReporter {
|
||||||
|
override fun setup(application: Application) {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun notify(throwable: Throwable) {
|
||||||
|
throw IllegalStateException("Who initliazed this? Without setup no calls to here!")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<meta-data
|
||||||
|
android:name="com.bugsnag.android.API_KEY"
|
||||||
|
android:value="${bugsnagApiKey}" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package eu.darken.capod.debug.autoreport
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
@Module
|
||||||
|
abstract class AutoReportingModule {
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun autoreporting(foss: GplayAutoReporting): AutomaticBugReporter
|
||||||
|
}
|
||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
package eu.darken.capod.common.debug.autoreport.bugsnag
|
package eu.darken.capod.debug.autoreport
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -7,7 +7,7 @@ import com.bugsnag.android.Event
|
|||||||
import com.bugsnag.android.OnErrorCallback
|
import com.bugsnag.android.OnErrorCallback
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import eu.darken.capod.common.BuildConfigWrap
|
import eu.darken.capod.common.BuildConfigWrap
|
||||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
import eu.darken.capod.common.debug.DebugSettings
|
||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
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.asLog
|
||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package eu.darken.capod.common.debug.autoreport.bugsnag
|
package eu.darken.capod.debug.autoreport
|
||||||
|
|
||||||
import com.bugsnag.android.Event
|
import com.bugsnag.android.Event
|
||||||
import eu.darken.capod.common.debug.logging.Logging
|
import eu.darken.capod.common.debug.logging.Logging
|
||||||
+20
-8
@@ -1,15 +1,16 @@
|
|||||||
package eu.darken.capod.common.debug.autoreport
|
package eu.darken.capod.debug.autoreport
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.bugsnag.android.Bugsnag
|
import com.bugsnag.android.Bugsnag
|
||||||
import com.bugsnag.android.Configuration
|
import com.bugsnag.android.Configuration
|
||||||
|
import com.getkeepsafe.relinker.ReLinker
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import eu.darken.capod.common.BuildConfigWrap
|
import eu.darken.capod.common.BuildConfigWrap
|
||||||
import eu.darken.capod.common.InstallId
|
import eu.darken.capod.common.InstallId
|
||||||
import eu.darken.capod.common.debug.Bugs
|
import eu.darken.capod.common.debug.Bugs
|
||||||
import eu.darken.capod.common.debug.autoreport.bugsnag.BugsnagErrorHandler
|
import eu.darken.capod.common.debug.DebugSettings
|
||||||
import eu.darken.capod.common.debug.autoreport.bugsnag.BugsnagLogger
|
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
|
||||||
import eu.darken.capod.common.debug.autoreport.bugsnag.NOPBugsnagErrorHandler
|
|
||||||
import eu.darken.capod.common.debug.logging.Logging
|
import eu.darken.capod.common.debug.logging.Logging
|
||||||
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
|
||||||
@@ -18,19 +19,25 @@ import javax.inject.Provider
|
|||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AutoReporting @Inject constructor(
|
class GplayAutoReporting @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val debugSettings: DebugSettings,
|
private val debugSettings: DebugSettings,
|
||||||
private val installId: InstallId,
|
private val installId: InstallId,
|
||||||
private val bugsnagLogger: Provider<BugsnagLogger>,
|
private val bugsnagLogger: Provider<BugsnagLogger>,
|
||||||
private val bugsnagErrorHandler: Provider<BugsnagErrorHandler>,
|
private val bugsnagErrorHandler: Provider<BugsnagErrorHandler>,
|
||||||
private val nopBugsnagErrorHandler: Provider<NOPBugsnagErrorHandler>,
|
private val nopBugsnagErrorHandler: Provider<NOPBugsnagErrorHandler>,
|
||||||
) {
|
) : AutomaticBugReporter {
|
||||||
|
|
||||||
fun setup() {
|
override fun setup(application: Application) {
|
||||||
val isEnabled = debugSettings.isAutoReportingEnabled.value
|
val isEnabled = debugSettings.isAutoReportingEnabled.value
|
||||||
log(TAG) { "setup(): isEnabled=$isEnabled" }
|
log(TAG) { "setup(): isEnabled=$isEnabled" }
|
||||||
|
|
||||||
|
if (!isEnabled) return
|
||||||
|
|
||||||
|
ReLinker
|
||||||
|
.log { message -> log(TAG) { "ReLinker: $message" } }
|
||||||
|
.loadLibrary(application, "bugsnag-plugin-android-anr")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val bugsnagConfig = Configuration.load(context).apply {
|
val bugsnagConfig = Configuration.load(context).apply {
|
||||||
if (debugSettings.isAutoReportingEnabled.value) {
|
if (debugSettings.isAutoReportingEnabled.value) {
|
||||||
@@ -48,13 +55,18 @@ class AutoReporting @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bugsnag.start(context, bugsnagConfig)
|
Bugsnag.start(context, bugsnagConfig)
|
||||||
Bugs.ready = true
|
Bugs.reporter = this
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
log(TAG) { "Bugsnag API Key not configured." }
|
log(TAG) { "Bugsnag API Key not configured." }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun notify(throwable: Throwable) {
|
||||||
|
Bugsnag.notify(throwable)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = logTag("Debug", "AutoReport")
|
private val TAG = logTag("Debug", "AutoReport")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package eu.darken.capod.common.debug.autoreport.bugsnag
|
package eu.darken.capod.debug.autoreport
|
||||||
|
|
||||||
import com.bugsnag.android.Event
|
import com.bugsnag.android.Event
|
||||||
import com.bugsnag.android.OnErrorCallback
|
import com.bugsnag.android.OnErrorCallback
|
||||||
@@ -2,7 +2,7 @@ package eu.darken.capod.common.bluetooth
|
|||||||
|
|
||||||
import dagger.Reusable
|
import dagger.Reusable
|
||||||
import eu.darken.capod.common.SystemClockWrap
|
import eu.darken.capod.common.SystemClockWrap
|
||||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
import eu.darken.capod.common.debug.DebugSettings
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@@ -27,9 +27,7 @@ class FakeBleData @Inject constructor(
|
|||||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
|
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
|
||||||
manufacturerSpecificData = mapOf(76 to "07 19 01 02 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".hexToByteArray())
|
manufacturerSpecificData = mapOf(76 to "07 19 01 02 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".hexToByteArray())
|
||||||
).run {
|
).run {
|
||||||
if (Random.nextBoolean()) {
|
fakeDevices.add(this)
|
||||||
fakeDevices.add(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AirPods Gen2
|
// AirPods Gen2
|
||||||
@@ -40,9 +38,7 @@ class FakeBleData @Inject constructor(
|
|||||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
|
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
|
||||||
manufacturerSpecificData = mapOf(76 to "07 19 01 0F 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".hexToByteArray())
|
manufacturerSpecificData = mapOf(76 to "07 19 01 0F 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".hexToByteArray())
|
||||||
).run {
|
).run {
|
||||||
if (Random.nextBoolean()) {
|
fakeDevices.add(this)
|
||||||
fakeDevices.add(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AirPods Gen3
|
// AirPods Gen3
|
||||||
@@ -53,9 +49,7 @@ class FakeBleData @Inject constructor(
|
|||||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 200,
|
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 200,
|
||||||
manufacturerSpecificData = mapOf(76 to "07 19 01 13 20 55 AF 56 31 00 06 6F E4 DF 10 AF 10 60 81 03 3B 76 D9 C7 11 22 88".hexToByteArray())
|
manufacturerSpecificData = mapOf(76 to "07 19 01 13 20 55 AF 56 31 00 06 6F E4 DF 10 AF 10 60 81 03 3B 76 D9 C7 11 22 88".hexToByteArray())
|
||||||
).run {
|
).run {
|
||||||
if (Random.nextBoolean()) {
|
fakeDevices.add(this)
|
||||||
fakeDevices.add(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// AirPods Max
|
// AirPods Max
|
||||||
BleScanResult(
|
BleScanResult(
|
||||||
@@ -65,9 +59,7 @@ class FakeBleData @Inject constructor(
|
|||||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 300,
|
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 300,
|
||||||
manufacturerSpecificData = mapOf(76 to "07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E".hexToByteArray())
|
manufacturerSpecificData = mapOf(76 to "07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E".hexToByteArray())
|
||||||
).run {
|
).run {
|
||||||
if (Random.nextBoolean()) {
|
fakeDevices.add(this)
|
||||||
fakeDevices.add(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// BeatsFlex
|
// BeatsFlex
|
||||||
BleScanResult(
|
BleScanResult(
|
||||||
@@ -77,9 +69,7 @@ class FakeBleData @Inject constructor(
|
|||||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
|
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
|
||||||
manufacturerSpecificData = mapOf(76 to "07 19 01 10 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".hexToByteArray())
|
manufacturerSpecificData = mapOf(76 to "07 19 01 10 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".hexToByteArray())
|
||||||
).run {
|
).run {
|
||||||
if (Random.nextBoolean()) {
|
fakeDevices.add(this)
|
||||||
fakeDevices.add(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tws i99999
|
// Tws i99999
|
||||||
@@ -90,9 +80,7 @@ class FakeBleData @Inject constructor(
|
|||||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
|
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
|
||||||
manufacturerSpecificData = mapOf(76 to "07 13 01 02 20 71 AA 37 32 00 10 00 64 64 FF 00 00 00 00 00 00".hexToByteArray())
|
manufacturerSpecificData = mapOf(76 to "07 13 01 02 20 71 AA 37 32 00 10 00 64 64 FF 00 00 00 00 00 00".hexToByteArray())
|
||||||
).run {
|
).run {
|
||||||
if (Random.nextBoolean()) {
|
fakeDevices.add(this)
|
||||||
fakeDevices.add(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unknown Device
|
// Unknown Device
|
||||||
@@ -103,9 +91,7 @@ class FakeBleData @Inject constructor(
|
|||||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 500,
|
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 500,
|
||||||
manufacturerSpecificData = mapOf(76 to "07 19 01 FF 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".hexToByteArray())
|
manufacturerSpecificData = mapOf(76 to "07 19 01 FF 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".hexToByteArray())
|
||||||
).run {
|
).run {
|
||||||
if (Random.nextBoolean()) {
|
fakeDevices.add(this)
|
||||||
fakeDevices.add(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fakeDevices
|
return fakeDevices
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package eu.darken.capod.common.collections
|
||||||
|
|
||||||
|
|
||||||
|
fun Collection<Int>.median(): Int = this.sorted().let {
|
||||||
|
if (it.size % 2 == 0) {
|
||||||
|
(it[it.size / 2] + it[(it.size - 1) / 2]) / 2
|
||||||
|
} else {
|
||||||
|
it[it.size / 2]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package eu.darken.capod.common.debug
|
package eu.darken.capod.common.debug
|
||||||
|
|
||||||
import com.bugsnag.android.Bugsnag
|
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
|
||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
||||||
import eu.darken.capod.common.debug.logging.asLog
|
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
|
||||||
|
|
||||||
object Bugs {
|
object Bugs {
|
||||||
var ready = false
|
var reporter: AutomaticBugReporter? = null
|
||||||
fun report(
|
fun report(
|
||||||
tag: String,
|
tag: String,
|
||||||
message: String,
|
message: String,
|
||||||
@@ -16,11 +16,9 @@ object Bugs {
|
|||||||
log(TAG, VERBOSE) { "Reporting $exception" }
|
log(TAG, VERBOSE) { "Reporting $exception" }
|
||||||
log(tag, ERROR) { "$message\n${exception.asLog()}" }
|
log(tag, ERROR) { "$message\n${exception.asLog()}" }
|
||||||
|
|
||||||
if (!ready) {
|
reporter?.notify(exception) ?: run {
|
||||||
log(TAG, WARN) { "Bug tracking not initialized yet." }
|
log(TAG, WARN) { "Bug tracking not initialized yet." }
|
||||||
return
|
|
||||||
}
|
}
|
||||||
Bugsnag.notify(exception)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val TAG = logTag("Bugs")
|
private val TAG = logTag("Bugs")
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package eu.darken.capod.common.debug.autoreport
|
package eu.darken.capod.common.debug
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package eu.darken.capod.common.debug.autoreport
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
|
||||||
|
interface AutomaticBugReporter {
|
||||||
|
|
||||||
|
fun setup(application: Application)
|
||||||
|
|
||||||
|
fun notify(throwable: Throwable)
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import androidx.preference.PreferenceDataStore
|
|||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import eu.darken.capod.common.bluetooth.ScannerMode
|
import eu.darken.capod.common.bluetooth.ScannerMode
|
||||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
import eu.darken.capod.common.debug.DebugSettings
|
||||||
import eu.darken.capod.common.preferences.PreferenceStoreMapper
|
import eu.darken.capod.common.preferences.PreferenceStoreMapper
|
||||||
import eu.darken.capod.common.preferences.Settings
|
import eu.darken.capod.common.preferences.Settings
|
||||||
import eu.darken.capod.common.preferences.createFlowPreference
|
import eu.darken.capod.common.preferences.createFlowPreference
|
||||||
@@ -26,7 +26,7 @@ class GeneralSettings @Inject constructor(
|
|||||||
val monitorMode = preferences.createFlowPreference("core.monitor.mode", MonitorMode.AUTOMATIC, moshi)
|
val monitorMode = preferences.createFlowPreference("core.monitor.mode", MonitorMode.AUTOMATIC, moshi)
|
||||||
val scannerMode = preferences.createFlowPreference("core.scanner.mode", ScannerMode.BALANCED, moshi)
|
val scannerMode = preferences.createFlowPreference("core.scanner.mode", ScannerMode.BALANCED, moshi)
|
||||||
|
|
||||||
val showAll = preferences.createFlowPreference("core.showall.enabled", false)
|
val showAll = preferences.createFlowPreference("core.showall.enabled", true)
|
||||||
|
|
||||||
val minimumSignalQuality = preferences.createFlowPreference("core.signal.minimum", 0.25f)
|
val minimumSignalQuality = preferences.createFlowPreference("core.signal.minimum", 0.25f)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import eu.darken.capod.common.bluetooth.BleScanner
|
|||||||
import eu.darken.capod.common.bluetooth.BluetoothManager2
|
import eu.darken.capod.common.bluetooth.BluetoothManager2
|
||||||
import eu.darken.capod.common.bluetooth.ScannerMode
|
import eu.darken.capod.common.bluetooth.ScannerMode
|
||||||
import eu.darken.capod.common.coroutine.AppScope
|
import eu.darken.capod.common.coroutine.AppScope
|
||||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
import eu.darken.capod.common.debug.DebugSettings
|
||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||||
import eu.darken.capod.common.debug.logging.asLog
|
import eu.darken.capod.common.debug.logging.asLog
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import com.squareup.moshi.Json
|
|||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import eu.darken.capod.common.R
|
import eu.darken.capod.common.R
|
||||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
|
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||||
|
import eu.darken.capod.common.debug.logging.log
|
||||||
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@@ -31,14 +34,20 @@ interface PodDevice {
|
|||||||
val rssi: Int
|
val rssi: Int
|
||||||
get() = scanResult.rssi
|
get() = scanResult.rssi
|
||||||
|
|
||||||
val confidence: Float
|
val reliability: Float
|
||||||
|
|
||||||
/**
|
|
||||||
* This is not correct but it works ¯\_(ツ)_/¯
|
|
||||||
* The range of the RSSI is device specific (ROMs).
|
|
||||||
*/
|
|
||||||
val signalQuality: Float
|
val signalQuality: Float
|
||||||
get() = ((100 - abs(rssi)) / 100f) * (max(BASE_CONFIDENCE, confidence))
|
get() {
|
||||||
|
/**
|
||||||
|
* This is not correct but it works ¯\_(ツ)_/¯
|
||||||
|
* The range of the RSSI is device specific (ROMs).
|
||||||
|
*/
|
||||||
|
val sqRssi = ((100 - abs(rssi)) / 100f)
|
||||||
|
val sqReliability = max(BASE_CONFIDENCE, reliability)
|
||||||
|
val sqAge = (Duration.between(seenFirstAt, Instant.now()).toMinutes().coerceAtMost(60) / 60f) * 0.25f
|
||||||
|
log(VERBOSE) { "Signal Quality ($address): rssi=$sqRssi, reliability=$reliability, age=$sqAge" }
|
||||||
|
return (sqRssi + sqReliability + sqAge) / 2f
|
||||||
|
}
|
||||||
|
|
||||||
val rawData: Map<Int, ByteArray>
|
val rawData: Map<Int, ByteArray>
|
||||||
get() = scanResult.manufacturerSpecificData
|
get() = scanResult.manufacturerSpecificData
|
||||||
@@ -133,6 +142,6 @@ interface PodDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val BASE_CONFIDENCE = 0.5f
|
const val BASE_CONFIDENCE = 0.0f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,8 +25,8 @@ fun SinglePodDevice.getBatteryLevelHeadset(context: Context): String =
|
|||||||
?: context.getString(R.string.general_value_not_available_label)
|
?: context.getString(R.string.general_value_not_available_label)
|
||||||
|
|
||||||
fun PodDevice.getSignalQuality(context: Context): String {
|
fun PodDevice.getSignalQuality(context: Context): String {
|
||||||
val percentage = 100 * signalQuality
|
val multiplier = 100 * signalQuality
|
||||||
return "~${percentage.roundToInt()}%"
|
return "${multiplier.roundToInt()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package eu.darken.capod.pods.core.apple
|
package eu.darken.capod.pods.core.apple
|
||||||
|
|
||||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||||
|
import eu.darken.capod.common.collections.median
|
||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
@@ -8,10 +9,10 @@ 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.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
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
abstract class ApplePodsFactory<PodType : ApplePods>(private val tag: String) {
|
abstract class ApplePodsFactory<PodType : ApplePods>(private val tag: String) {
|
||||||
|
|
||||||
@@ -38,7 +39,8 @@ abstract class ApplePodsFactory<PodType : ApplePods>(private val tag: String) {
|
|||||||
val id: PodDevice.Id,
|
val id: PodDevice.Id,
|
||||||
val seenFirstAt: Instant,
|
val seenFirstAt: Instant,
|
||||||
val seenCounter: Int,
|
val seenCounter: Int,
|
||||||
val history: List<ApplePods>
|
val history: List<ApplePods>,
|
||||||
|
val lastCaseBattery: Float?,
|
||||||
) {
|
) {
|
||||||
val lastMessage: ProximityPairing.Message
|
val lastMessage: ProximityPairing.Message
|
||||||
get() = history.last().proximityMessage
|
get() = history.last().proximityMessage
|
||||||
@@ -46,63 +48,81 @@ abstract class ApplePodsFactory<PodType : ApplePods>(private val tag: String) {
|
|||||||
val lastAddress: String
|
val lastAddress: String
|
||||||
get() = history.last().address
|
get() = history.last().address
|
||||||
|
|
||||||
val confidence: Float
|
val reliability: Float
|
||||||
get() = 0.75f + (history.size / (MAX_HISTORY * 4f))
|
get() {
|
||||||
|
if (history.size < 2) return 0f
|
||||||
|
|
||||||
fun averageRssi(latest: Int): Int =
|
val now = Instant.now()
|
||||||
history.map { it.rssi }.plus(latest).takeLast(10).median()
|
|
||||||
|
val pingInterval = List(history.dropLast(1).size) { index ->
|
||||||
|
Duration.between(history[index].seenLastAt, history[index + 1].seenLastAt).toMillis()
|
||||||
|
}.map { it.toInt() }.median()
|
||||||
|
|
||||||
|
val expectedPings: Float = LOOKBACK.toMillis() / pingInterval.toFloat()
|
||||||
|
|
||||||
|
val recentPings = history.filter {
|
||||||
|
Duration.between(it.seenLastAt, now).toMillis() < LOOKBACK.toMillis() + pingInterval
|
||||||
|
}.size
|
||||||
|
|
||||||
|
val reliability: Float = (recentPings / expectedPings)
|
||||||
|
// log("#####") { "interval=$pingInterval, expectedPerMinute=$expectedPings, count=$recentPings" }
|
||||||
|
|
||||||
|
return max(0.0f, reliability).coerceAtMost(1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rssiSmoothed(latest: Int): Int {
|
||||||
|
val now = Instant.now()
|
||||||
|
return history
|
||||||
|
.filter { Duration.between(it.seenLastAt, now) < LOOKBACK }
|
||||||
|
.map { it.rssi }
|
||||||
|
.plus(latest)
|
||||||
|
.median()
|
||||||
|
}
|
||||||
|
|
||||||
fun isOlderThan(age: Duration): Boolean {
|
fun isOlderThan(age: Duration): Boolean {
|
||||||
val now = Instant.now()
|
val now = Instant.now()
|
||||||
return Duration.between(history.last().seenLastAt, now) > age
|
return Duration.between(history.last().seenLastAt, now) > age
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Int>.median(): Int = this.sorted().let {
|
|
||||||
if (it.size % 2 == 0)
|
|
||||||
(it[it.size / 2] + it[(it.size - 1) / 2]) / 2
|
|
||||||
else
|
|
||||||
it[it.size / 2]
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = "KnownDevice(history=${history.size}, last=${history.last()})"
|
override fun toString(): String = "KnownDevice(history=${history.size}, last=${history.last()})"
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val MAX_HISTORY = 20
|
// 30 seconds at fastest interval
|
||||||
|
const val MAX_HISTORY = 60
|
||||||
|
val LOOKBACK = Duration.ofSeconds(30)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val knownDevices = mutableMapOf<PodDevice.Id, KnownDevice>()
|
internal val knownDevices = mutableMapOf<PodDevice.Id, KnownDevice>()
|
||||||
|
|
||||||
|
|
||||||
fun KnownDevice.getLatestCaseBattery(): Float? = history
|
fun KnownDevice.getLatestCaseBattery(): Float? = this.lastCaseBattery
|
||||||
|
|
||||||
|
private fun Collection<ApplePods>.determineLatestCaseBattery(): Float? = this
|
||||||
.filterIsInstance<HasCase>()
|
.filterIsInstance<HasCase>()
|
||||||
.mapNotNull { it.batteryCasePercent }
|
.mapNotNull { it.batteryCasePercent }
|
||||||
.lastOrNull()
|
.lastOrNull()
|
||||||
|
|
||||||
fun KnownDevice.getLatestCaseLidState(basic: DualApplePods): DualApplePods.LidState? {
|
fun KnownDevice.getLatestCaseLidState(basic: DualApplePods): DualApplePods.LidState? {
|
||||||
val definitive = setOf(DualApplePods.LidState.OPEN, DualApplePods.LidState.CLOSED)
|
val definitive = setOf(
|
||||||
if (definitive.contains(basic.caseLidState)) return null
|
DualApplePods.LidState.OPEN,
|
||||||
|
DualApplePods.LidState.CLOSED,
|
||||||
|
DualApplePods.LidState.NOT_IN_CASE,
|
||||||
|
)
|
||||||
|
if (definitive.contains(basic.caseLidState)) return basic.caseLidState
|
||||||
|
|
||||||
return history
|
return history
|
||||||
.filterIsInstance<AirPodsPro>() // TODO why is this AirPodsPro specific here?
|
.takeLast(2)
|
||||||
.lastOrNull { it.caseLidState != DualApplePods.LidState.NOT_IN_CASE }
|
.filterIsInstance<DualApplePods>()
|
||||||
|
.lastOrNull { it.caseLidState != DualApplePods.LidState.UNKNOWN }
|
||||||
?.caseLidState
|
?.caseLidState
|
||||||
|
?: DualApplePods.LidState.NOT_IN_CASE
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun historyTrimmer(
|
open fun historyTrimmer(
|
||||||
pods: List<ApplePods>
|
pods: List<ApplePods>
|
||||||
): List<ApplePods> {
|
): List<ApplePods> {
|
||||||
var trimmed = pods.takeLast(KnownDevice.MAX_HISTORY)
|
return pods.takeLast(KnownDevice.MAX_HISTORY)
|
||||||
|
|
||||||
// We want to keep the case information
|
|
||||||
// If both pods are removed, and produce more history, we otherwise lose the case battery info.
|
|
||||||
val caseInfoFat = pods.lastOrNull { it is HasCase && it.batteryCasePercent != null }
|
|
||||||
val caseInfoTrimmed = trimmed.lastOrNull { it is HasCase && it.batteryCasePercent != null }
|
|
||||||
if (caseInfoFat != null && caseInfoTrimmed == null) {
|
|
||||||
trimmed = listOf(caseInfoFat) + trimmed.takeLast(KnownDevice.MAX_HISTORY - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return trimmed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal open fun searchHistory(current: PodType): KnownDevice? {
|
internal open fun searchHistory(current: PodType): KnownDevice? {
|
||||||
@@ -110,7 +130,7 @@ abstract class ApplePodsFactory<PodType : ApplePods>(private val tag: String) {
|
|||||||
val message = current.proximityMessage
|
val message = current.proximityMessage
|
||||||
|
|
||||||
knownDevices.values.toList().forEach { knownDevice ->
|
knownDevices.values.toList().forEach { knownDevice ->
|
||||||
if (knownDevice.isOlderThan(Duration.ofSeconds(20))) {
|
if (knownDevice.isOlderThan(Duration.ofSeconds(30))) {
|
||||||
log(tag, VERBOSE) { "searchHistory1: Removing stale known device: $knownDevice" }
|
log(tag, VERBOSE) { "searchHistory1: Removing stale known device: $knownDevice" }
|
||||||
knownDevices.remove(knownDevice.id)
|
knownDevices.remove(knownDevice.id)
|
||||||
}
|
}
|
||||||
@@ -146,18 +166,22 @@ abstract class ApplePodsFactory<PodType : ApplePods>(private val tag: String) {
|
|||||||
|
|
||||||
knownDevices[device.identifier] = when {
|
knownDevices[device.identifier] = when {
|
||||||
existing != null -> {
|
existing != null -> {
|
||||||
|
val history = existing.history.plus(device)
|
||||||
existing.copy(
|
existing.copy(
|
||||||
seenCounter = existing.seenCounter + 1,
|
seenCounter = existing.seenCounter + 1,
|
||||||
history = existing.history.plus(device)
|
history = history,
|
||||||
|
lastCaseBattery = history.determineLatestCaseBattery() ?: existing.lastCaseBattery
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
log(tag) { "searchHistory1: Creating new history for $device" }
|
log(tag) { "searchHistory1: Creating new history for $device" }
|
||||||
|
val history = listOf(device)
|
||||||
KnownDevice(
|
KnownDevice(
|
||||||
id = device.identifier,
|
id = device.identifier,
|
||||||
seenFirstAt = device.seenFirstAt,
|
seenFirstAt = device.seenFirstAt,
|
||||||
seenCounter = 1,
|
seenCounter = 1,
|
||||||
history = listOf(device)
|
history = history,
|
||||||
|
lastCaseBattery = history.determineLatestCaseBattery()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ data class AirPodsGen1 constructor(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: 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: DualApplePods.LidState? = null
|
||||||
@@ -55,9 +55,9 @@ data class AirPodsGen1 constructor(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
cachedCaseState = result.getLatestCaseLidState(basic)
|
cachedCaseState = result.getLatestCaseLidState(basic)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ data class AirPodsGen2 constructor(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: 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: DualApplePods.LidState? = null
|
||||||
@@ -54,9 +54,9 @@ data class AirPodsGen2 constructor(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
cachedCaseState = result.getLatestCaseLidState(basic)
|
cachedCaseState = result.getLatestCaseLidState(basic)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ data class AirPodsGen3 constructor(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: 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: DualApplePods.LidState? = null
|
||||||
@@ -54,9 +54,9 @@ data class AirPodsGen3 constructor(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
cachedCaseState = result.getLatestCaseLidState(basic)
|
cachedCaseState = result.getLatestCaseLidState(basic)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ data class AirPodsMax(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : SingleApplePods, HasEarDetection, HasAppleColor {
|
) : SingleApplePods, HasEarDetection, HasAppleColor {
|
||||||
|
|
||||||
@@ -58,8 +58,8 @@ data class AirPodsMax(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ data class AirPodsPro(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: 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: LidState? = null
|
private val cachedCaseState: LidState? = null
|
||||||
@@ -73,9 +73,9 @@ data class AirPodsPro(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
cachedCaseState = result.getLatestCaseLidState(basic)
|
cachedCaseState = result.getLatestCaseLidState(basic)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ data class AirPodsPro2(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: 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: LidState? = null
|
private val cachedCaseState: LidState? = null
|
||||||
@@ -73,9 +73,9 @@ data class AirPodsPro2(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
cachedCaseState = result.getLatestCaseLidState(basic)
|
cachedCaseState = result.getLatestCaseLidState(basic)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ data class BeatsFitPro(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: 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: DualApplePods.LidState? = null
|
||||||
@@ -54,9 +54,9 @@ data class BeatsFitPro(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
cachedCaseState = result.getLatestCaseLidState(basic)
|
cachedCaseState = result.getLatestCaseLidState(basic)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ data class BeatsFlex(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : SingleApplePods {
|
) : SingleApplePods {
|
||||||
|
|
||||||
@@ -46,8 +46,8 @@ data class BeatsFlex(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ data class BeatsSolo3(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : SingleApplePods {
|
) : SingleApplePods {
|
||||||
|
|
||||||
@@ -45,8 +45,8 @@ data class BeatsSolo3(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ data class BeatsStudio3(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : SingleApplePods {
|
) : SingleApplePods {
|
||||||
|
|
||||||
@@ -43,8 +43,8 @@ data class BeatsStudio3(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ data class BeatsX(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : SingleApplePods {
|
) : SingleApplePods {
|
||||||
|
|
||||||
@@ -46,8 +46,8 @@ data class BeatsX(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ data class PowerBeats3(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : SingleApplePods {
|
) : SingleApplePods {
|
||||||
|
|
||||||
@@ -46,8 +46,8 @@ data class PowerBeats3(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ data class PowerBeatsPro(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: 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: DualApplePods.LidState? = null
|
||||||
@@ -54,9 +54,9 @@ data class PowerBeatsPro(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
cachedCaseState = result.getLatestCaseLidState(basic)
|
cachedCaseState = result.getLatestCaseLidState(basic)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ data class FakeAirPodsGen1 constructor(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
private val cachedBatteryPercentage: Float? = null,
|
private val cachedBatteryPercentage: Float? = null,
|
||||||
) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
||||||
@@ -138,9 +138,9 @@ data class FakeAirPodsGen1 constructor(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ data class FakeAirPodsGen2 constructor(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
private val cachedBatteryPercentage: Float? = null,
|
private val cachedBatteryPercentage: Float? = null,
|
||||||
) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
||||||
@@ -136,9 +136,9 @@ data class FakeAirPodsGen2 constructor(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ data class FakeAirPodsGen3 constructor(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
private val cachedBatteryPercentage: Float? = null,
|
private val cachedBatteryPercentage: Float? = null,
|
||||||
) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
||||||
@@ -138,9 +138,9 @@ data class FakeAirPodsGen3 constructor(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ data class FakeAirPodsPro constructor(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
private val cachedBatteryPercentage: Float? = null,
|
private val cachedBatteryPercentage: Float? = null,
|
||||||
) : ApplePods, DualPodDevice, HasChargeDetectionDual, HasEarDetectionDual, HasCase {
|
) : ApplePods, DualPodDevice, HasChargeDetectionDual, HasEarDetectionDual, HasCase {
|
||||||
@@ -156,9 +156,9 @@ data class FakeAirPodsPro constructor(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ data class FakeAirPodsPro2 constructor(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = PodDevice.BASE_CONFIDENCE,
|
override val reliability: 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: DualApplePods.LidState? = null,
|
||||||
@@ -153,9 +153,9 @@ data class FakeAirPodsPro2 constructor(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ data class UnknownAppleDevice(
|
|||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val proximityMessage: ProximityPairing.Message,
|
override val proximityMessage: ProximityPairing.Message,
|
||||||
override val confidence: Float = 0f,
|
override val reliability: Float = 0f,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : ApplePods {
|
) : ApplePods {
|
||||||
|
|
||||||
@@ -49,8 +49,8 @@ data class UnknownAppleDevice(
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.reliability,
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ data class UnknownDevice(
|
|||||||
override val seenFirstAt: Instant = Instant.now(),
|
override val seenFirstAt: Instant = Instant.now(),
|
||||||
override val seenCounter: Int = 1,
|
override val seenCounter: Int = 1,
|
||||||
override val scanResult: BleScanResult,
|
override val scanResult: BleScanResult,
|
||||||
override val confidence: Float = 0f,
|
override val reliability: Float = 0f,
|
||||||
private val rssiAverage: Int? = null,
|
private val rssiAverage: Int? = null,
|
||||||
) : PodDevice {
|
) : PodDevice {
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ class UnknownDeviceFactory @Inject constructor() {
|
|||||||
seenFirstAt = result.seenFirstAt,
|
seenFirstAt = result.seenFirstAt,
|
||||||
seenLastAt = scanResult.receivedAt,
|
seenLastAt = scanResult.receivedAt,
|
||||||
seenCounter = result.seenCounter,
|
seenCounter = result.seenCounter,
|
||||||
confidence = result.confidence,
|
reliability = result.confidence,
|
||||||
rssiAverage = result.averageRssi(basic.rssi),
|
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ class UnknownDeviceFactory @Inject constructor() {
|
|||||||
val confidence: Float
|
val confidence: Float
|
||||||
get() = 0.75f + (history.size / (MAX_HISTORY * 4f))
|
get() = 0.75f + (history.size / (MAX_HISTORY * 4f))
|
||||||
|
|
||||||
fun averageRssi(latest: Int): Int =
|
fun rssiSmoothed(latest: Int): Int =
|
||||||
history.map { it.rssi }.plus(latest).takeLast(10).median()
|
history.map { it.rssi }.plus(latest).takeLast(10).median()
|
||||||
|
|
||||||
fun isOlderThan(age: Duration): Boolean {
|
fun isOlderThan(age: Duration): Boolean {
|
||||||
@@ -70,7 +70,7 @@ class UnknownDeviceFactory @Inject constructor() {
|
|||||||
override fun toString(): String = "KnownDevice(history=${history.size}, last=${history.last()})"
|
override fun toString(): String = "KnownDevice(history=${history.size}, last=${history.last()})"
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val MAX_HISTORY = 10
|
const val MAX_HISTORY = 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ plugins {
|
|||||||
}
|
}
|
||||||
apply(plugin = "dagger.hilt.android.plugin")
|
apply(plugin = "dagger.hilt.android.plugin")
|
||||||
apply(plugin = "androidx.navigation.safeargs.kotlin")
|
apply(plugin = "androidx.navigation.safeargs.kotlin")
|
||||||
apply(plugin = "com.bugsnag.android.gradle")
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk = ProjectConfig.compileSdk
|
compileSdk = ProjectConfig.compileSdk
|
||||||
@@ -24,10 +23,6 @@ android {
|
|||||||
|
|
||||||
buildConfigField("String", "GITSHA", "\"${lastCommitHash()}\"")
|
buildConfigField("String", "GITSHA", "\"${lastCommitHash()}\"")
|
||||||
buildConfigField("String", "BUILDTIME", "\"${buildTime()}\"")
|
buildConfigField("String", "BUILDTIME", "\"${buildTime()}\"")
|
||||||
|
|
||||||
manifestPlaceholders["bugsnagApiKey"] = getBugSnagApiKey(
|
|
||||||
File(System.getProperty("user.home"), ".appconfig/${ProjectConfig.packageName}/bugsnag.properties")
|
|
||||||
) ?: "fake"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
@@ -49,9 +44,12 @@ android {
|
|||||||
create("gplay") {
|
create("gplay") {
|
||||||
dimension = "version"
|
dimension = "version"
|
||||||
signingConfig = signingConfigs["releaseGplay"]
|
signingConfig = signingConfigs["releaseGplay"]
|
||||||
|
extra["useBugsnag"] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupBugsnagPlugin()
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
val customProguardRules = fileTree(File("../proguard")) {
|
val customProguardRules = fileTree(File("../proguard")) {
|
||||||
include("*.pro")
|
include("*.pro")
|
||||||
@@ -92,7 +90,7 @@ android {
|
|||||||
if (listOf("release", "beta").any { variantName.toLowerCase().contains(it) }) {
|
if (listOf("release", "beta").any { variantName.toLowerCase().contains(it) }) {
|
||||||
val outputFileName = ProjectConfig.packageName + "-WEAROS" +
|
val outputFileName = ProjectConfig.packageName + "-WEAROS" +
|
||||||
"-v${defaultConfig.versionName}-${defaultConfig.versionCode}" +
|
"-v${defaultConfig.versionName}-${defaultConfig.versionCode}" +
|
||||||
"-${variantName.toUpperCase()}-${lastCommitHash()}.apk"
|
"-${variantName.toUpperCase()}.apk"
|
||||||
|
|
||||||
variantOutputImpl.outputFileName = outputFileName
|
variantOutputImpl.outputFileName = outputFileName
|
||||||
}
|
}
|
||||||
@@ -138,9 +136,9 @@ dependencies {
|
|||||||
|
|
||||||
addTesting()
|
addTesting()
|
||||||
|
|
||||||
implementation("com.bugsnag:bugsnag-android:5.9.2")
|
|
||||||
implementation("com.getkeepsafe.relinker:relinker:1.4.3")
|
|
||||||
|
|
||||||
implementation("androidx.wear:wear:1.2.0")
|
implementation("androidx.wear:wear:1.2.0")
|
||||||
implementation("androidx.wear.tiles:tiles-material:1.1.0")
|
implementation("androidx.wear.tiles:tiles-material:1.1.0")
|
||||||
|
|
||||||
|
"gplayImplementation"("com.bugsnag:bugsnag-android:5.9.2")
|
||||||
|
"gplayImplementation"("com.getkeepsafe.relinker:relinker:1.4.3")
|
||||||
}
|
}
|
||||||
@@ -39,10 +39,6 @@
|
|||||||
android:name="androidx.startup.InitializationProvider"
|
android:name="androidx.startup.InitializationProvider"
|
||||||
android:authorities="${applicationId}.androidx-startup"
|
android:authorities="${applicationId}.androidx-startup"
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="com.bugsnag.android.API_KEY"
|
|
||||||
android:value="${bugsnagApiKey}" />
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -3,11 +3,10 @@ package eu.darken.capod
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.hilt.work.HiltWorkerFactory
|
import androidx.hilt.work.HiltWorkerFactory
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
import com.getkeepsafe.relinker.ReLinker
|
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
import eu.darken.capod.common.BuildConfigWrap
|
import eu.darken.capod.common.BuildConfigWrap
|
||||||
import eu.darken.capod.common.coroutine.AppScope
|
import eu.darken.capod.common.coroutine.AppScope
|
||||||
import eu.darken.capod.common.debug.autoreport.AutoReporting
|
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
|
||||||
import eu.darken.capod.common.debug.logging.*
|
import eu.darken.capod.common.debug.logging.*
|
||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||||
import eu.darken.capod.main.core.GeneralSettings
|
import eu.darken.capod.main.core.GeneralSettings
|
||||||
@@ -20,7 +19,7 @@ import javax.inject.Inject
|
|||||||
open class App : Application(), Configuration.Provider {
|
open class App : Application(), Configuration.Provider {
|
||||||
|
|
||||||
@Inject lateinit var workerFactory: HiltWorkerFactory
|
@Inject lateinit var workerFactory: HiltWorkerFactory
|
||||||
@Inject lateinit var autoReporting: AutoReporting
|
@Inject lateinit var autoReporting: AutomaticBugReporter
|
||||||
@Inject lateinit var generalSettings: GeneralSettings
|
@Inject lateinit var generalSettings: GeneralSettings
|
||||||
@Inject lateinit var workManager: WorkManager
|
@Inject lateinit var workManager: WorkManager
|
||||||
@Inject @AppScope lateinit var appScope: CoroutineScope
|
@Inject @AppScope lateinit var appScope: CoroutineScope
|
||||||
@@ -29,11 +28,7 @@ open class App : Application(), Configuration.Provider {
|
|||||||
super.onCreate()
|
super.onCreate()
|
||||||
if (BuildConfig.DEBUG) Logging.install(LogCatLogger())
|
if (BuildConfig.DEBUG) Logging.install(LogCatLogger())
|
||||||
|
|
||||||
ReLinker
|
autoReporting.setup(this)
|
||||||
.log { message -> log(TAG) { "ReLinker: $message" } }
|
|
||||||
.loadLibrary(this, "bugsnag-plugin-android-anr")
|
|
||||||
|
|
||||||
autoReporting.setup()
|
|
||||||
|
|
||||||
setupWorker()
|
setupWorker()
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import eu.darken.capod.common.bluetooth.BluetoothManager2
|
import eu.darken.capod.common.bluetooth.BluetoothManager2
|
||||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
import eu.darken.capod.common.debug.DebugSettings
|
||||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
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.flow.combine
|
import eu.darken.capod.common.flow.combine
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ plugins {
|
|||||||
}
|
}
|
||||||
apply(plugin = "dagger.hilt.android.plugin")
|
apply(plugin = "dagger.hilt.android.plugin")
|
||||||
apply(plugin = "androidx.navigation.safeargs.kotlin")
|
apply(plugin = "androidx.navigation.safeargs.kotlin")
|
||||||
apply(plugin = "com.bugsnag.android.gradle")
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
||||||
@@ -22,10 +21,6 @@ android {
|
|||||||
versionName = ProjectConfig.Version.name
|
versionName = ProjectConfig.Version.name
|
||||||
|
|
||||||
testInstrumentationRunner = "eu.darken.capod.HiltTestRunner"
|
testInstrumentationRunner = "eu.darken.capod.HiltTestRunner"
|
||||||
|
|
||||||
manifestPlaceholders["bugsnagApiKey"] = getBugSnagApiKey(
|
|
||||||
File(System.getProperty("user.home"), ".appconfig/${ProjectConfig.packageName}/bugsnag.properties")
|
|
||||||
) ?: "fake"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
@@ -47,9 +42,12 @@ android {
|
|||||||
create("gplay") {
|
create("gplay") {
|
||||||
dimension = "version"
|
dimension = "version"
|
||||||
signingConfig = signingConfigs["releaseGplay"]
|
signingConfig = signingConfigs["releaseGplay"]
|
||||||
|
extra["useBugsnag"] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupBugsnagPlugin()
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
val customProguardRules = fileTree(File(projectDir, "proguard")) {
|
val customProguardRules = fileTree(File(projectDir, "proguard")) {
|
||||||
include("*.pro")
|
include("*.pro")
|
||||||
@@ -90,7 +88,7 @@ android {
|
|||||||
if (listOf("release", "beta").any { variantName.toLowerCase().contains(it) }) {
|
if (listOf("release", "beta").any { variantName.toLowerCase().contains(it) }) {
|
||||||
val outputFileName = ProjectConfig.packageName +
|
val outputFileName = ProjectConfig.packageName +
|
||||||
"-v${defaultConfig.versionName}-${defaultConfig.versionCode}" +
|
"-v${defaultConfig.versionName}-${defaultConfig.versionCode}" +
|
||||||
"-${variantName.toUpperCase()}-${lastCommitHash()}.apk"
|
"-${variantName.toUpperCase()}.apk"
|
||||||
|
|
||||||
variantOutputImpl.outputFileName = outputFileName
|
variantOutputImpl.outputFileName = outputFileName
|
||||||
}
|
}
|
||||||
@@ -148,8 +146,8 @@ dependencies {
|
|||||||
|
|
||||||
addTesting()
|
addTesting()
|
||||||
|
|
||||||
implementation("com.bugsnag:bugsnag-android:5.9.2")
|
|
||||||
implementation("com.getkeepsafe.relinker:relinker:1.4.3")
|
|
||||||
|
|
||||||
"gplayImplementation"("com.android.billingclient:billing:4.0.0")
|
"gplayImplementation"("com.android.billingclient:billing:4.0.0")
|
||||||
|
|
||||||
|
"gplayImplementation"("com.bugsnag:bugsnag-android:5.9.2")
|
||||||
|
"gplayImplementation"("com.getkeepsafe.relinker:relinker:1.4.3")
|
||||||
}
|
}
|
||||||
@@ -86,10 +86,6 @@
|
|||||||
android:name="androidx.startup.InitializationProvider"
|
android:name="androidx.startup.InitializationProvider"
|
||||||
android:authorities="${applicationId}.androidx-startup"
|
android:authorities="${applicationId}.androidx-startup"
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="com.bugsnag.android.API_KEY"
|
|
||||||
android:value="${bugsnagApiKey}" />
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -3,10 +3,9 @@ package eu.darken.capod
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.hilt.work.HiltWorkerFactory
|
import androidx.hilt.work.HiltWorkerFactory
|
||||||
import androidx.work.Configuration
|
import androidx.work.Configuration
|
||||||
import com.getkeepsafe.relinker.ReLinker
|
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
import eu.darken.capod.common.coroutine.AppScope
|
import eu.darken.capod.common.coroutine.AppScope
|
||||||
import eu.darken.capod.common.debug.autoreport.AutoReporting
|
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
|
||||||
import eu.darken.capod.common.debug.logging.*
|
import eu.darken.capod.common.debug.logging.*
|
||||||
import eu.darken.capod.common.flow.throttleLatest
|
import eu.darken.capod.common.flow.throttleLatest
|
||||||
import eu.darken.capod.common.upgrade.UpgradeRepo
|
import eu.darken.capod.common.upgrade.UpgradeRepo
|
||||||
@@ -25,7 +24,7 @@ import javax.inject.Inject
|
|||||||
open class App : Application(), Configuration.Provider {
|
open class App : Application(), Configuration.Provider {
|
||||||
|
|
||||||
@Inject lateinit var workerFactory: HiltWorkerFactory
|
@Inject lateinit var workerFactory: HiltWorkerFactory
|
||||||
@Inject lateinit var autoReporting: AutoReporting
|
@Inject lateinit var autoReporting: AutomaticBugReporter
|
||||||
@Inject lateinit var monitorControl: MonitorControl
|
@Inject lateinit var monitorControl: MonitorControl
|
||||||
@Inject lateinit var podMonitor: PodMonitor
|
@Inject lateinit var podMonitor: PodMonitor
|
||||||
@Inject lateinit var widgetManager: WidgetManager
|
@Inject lateinit var widgetManager: WidgetManager
|
||||||
@@ -36,11 +35,7 @@ open class App : Application(), Configuration.Provider {
|
|||||||
super.onCreate()
|
super.onCreate()
|
||||||
if (BuildConfig.DEBUG) Logging.install(LogCatLogger())
|
if (BuildConfig.DEBUG) Logging.install(LogCatLogger())
|
||||||
|
|
||||||
ReLinker
|
autoReporting.setup(this)
|
||||||
.log { message -> log(TAG) { "ReLinker: $message" } }
|
|
||||||
.loadLibrary(this, "bugsnag-plugin-android-anr")
|
|
||||||
|
|
||||||
autoReporting.setup()
|
|
||||||
|
|
||||||
log(TAG) { "onCreate() done! ${Exception().asLog()}" }
|
log(TAG) { "onCreate() done! ${Exception().asLog()}" }
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import eu.darken.capod.common.bluetooth.BluetoothManager2
|
import eu.darken.capod.common.bluetooth.BluetoothManager2
|
||||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
import eu.darken.capod.common.debug.DebugSettings
|
||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
import eu.darken.capod.common.flow.combine
|
import eu.darken.capod.common.flow.combine
|
||||||
import eu.darken.capod.common.flow.throttleLatest
|
import eu.darken.capod.common.flow.throttleLatest
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import androidx.fragment.app.viewModels
|
|||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
import eu.darken.capod.common.debug.DebugSettings
|
||||||
import eu.darken.capod.common.observe2
|
import eu.darken.capod.common.observe2
|
||||||
import eu.darken.capod.common.uix.PreferenceFragment3
|
import eu.darken.capod.common.uix.PreferenceFragment3
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ package eu.darken.capod.main.ui.settings.general.debug
|
|||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
import eu.darken.capod.common.debug.DebugSettings
|
||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.common.debug.recording.core.RecorderModule
|
import eu.darken.capod.common.debug.recording.core.RecorderModule
|
||||||
|
|||||||
@@ -37,15 +37,13 @@ class WidgetProvider : AppWidgetProvider() {
|
|||||||
@Inject lateinit var upgradeRepo: UpgradeRepo
|
@Inject lateinit var upgradeRepo: UpgradeRepo
|
||||||
@AppScope @Inject lateinit var appScope: CoroutineScope
|
@AppScope @Inject lateinit var appScope: CoroutineScope
|
||||||
|
|
||||||
private var asyncBarrier: PendingResult? = null
|
|
||||||
|
|
||||||
private fun executeAsync(
|
private fun executeAsync(
|
||||||
tag: String,
|
tag: String,
|
||||||
timeout: Duration = Duration.ofSeconds(10),
|
timeout: Duration = Duration.ofSeconds(8),
|
||||||
block: suspend () -> Unit
|
block: suspend () -> Unit
|
||||||
) {
|
) {
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
asyncBarrier = goAsync()
|
val asyncBarrier = goAsync()
|
||||||
log(TAG, VERBOSE) { "executeAsync($tag) starting asyncBarrier=$asyncBarrier " }
|
log(TAG, VERBOSE) { "executeAsync($tag) starting asyncBarrier=$asyncBarrier " }
|
||||||
|
|
||||||
appScope.launch {
|
appScope.launch {
|
||||||
@@ -54,7 +52,7 @@ class WidgetProvider : AppWidgetProvider() {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log(TAG, ERROR) { "executeAsync($tag) failed: ${e.asLog()}" }
|
log(TAG, ERROR) { "executeAsync($tag) failed: ${e.asLog()}" }
|
||||||
} finally {
|
} finally {
|
||||||
asyncBarrier?.finish()
|
asyncBarrier.finish()
|
||||||
val stop = System.currentTimeMillis()
|
val stop = System.currentTimeMillis()
|
||||||
log(TAG, VERBOSE) { "executeAsync($tag) DONE (${stop - start}ms) " }
|
log(TAG, VERBOSE) { "executeAsync($tag) DONE (${stop - start}ms) " }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ 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.DualApplePods
|
|
||||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
@@ -16,12 +14,12 @@ class MonitorNotificationViewFactory @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
fun createContentView(device: PodDevice): RemoteViews = when (device) {
|
fun createContentView(device: PodDevice): RemoteViews = when (device) {
|
||||||
is DualApplePods -> createDualApplePods(device)
|
is DualPodDevice -> createDualPods(device)
|
||||||
is SingleApplePods -> createSingleApplePods(device)
|
is SinglePodDevice -> createSinglePod(device)
|
||||||
else -> createUnknownDevice(device)
|
else -> createUnknownDevice(device)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDualApplePods(device: DualApplePods): RemoteViews = RemoteViews(
|
private fun createDualPods(device: DualPodDevice): RemoteViews = RemoteViews(
|
||||||
context.packageName,
|
context.packageName,
|
||||||
R.layout.monitor_notification_dual_pods_small
|
R.layout.monitor_notification_dual_pods_small
|
||||||
).apply {
|
).apply {
|
||||||
@@ -29,23 +27,30 @@ class MonitorNotificationViewFactory @Inject constructor(
|
|||||||
// Left
|
// Left
|
||||||
setImageViewResource(R.id.pod_left_icon, device.leftPodIcon)
|
setImageViewResource(R.id.pod_left_icon, device.leftPodIcon)
|
||||||
setTextViewText(R.id.pod_left_label, getBatteryLevelLeftPod(context))
|
setTextViewText(R.id.pod_left_label, getBatteryLevelLeftPod(context))
|
||||||
|
val isLeftPodCharging = (device as? HasChargeDetectionDual)?.isLeftPodCharging ?: false
|
||||||
setViewVisibility(R.id.pod_left_charging, if (isLeftPodCharging) View.VISIBLE else View.GONE)
|
setViewVisibility(R.id.pod_left_charging, if (isLeftPodCharging) View.VISIBLE else View.GONE)
|
||||||
|
val isLeftPodInEar = (device as? HasEarDetectionDual)?.isLeftPodInEar ?: false
|
||||||
setViewVisibility(R.id.pod_left_ear, if (isLeftPodInEar) View.VISIBLE else View.GONE)
|
setViewVisibility(R.id.pod_left_ear, if (isLeftPodInEar) View.VISIBLE else View.GONE)
|
||||||
|
|
||||||
// Case
|
// Case
|
||||||
setImageViewResource(R.id.pod_case_icon, device.caseIcon)
|
setViewVisibility(R.id.pod_case_charging, if (device is HasCase) View.VISIBLE else View.GONE)
|
||||||
setTextViewText(R.id.pod_case_label, getBatteryLevelCase(context))
|
(device as? HasCase)?.let { case ->
|
||||||
setViewVisibility(R.id.pod_case_charging, if (isCaseCharging) View.VISIBLE else View.GONE)
|
setImageViewResource(R.id.pod_case_icon, device.caseIcon)
|
||||||
|
setTextViewText(R.id.pod_case_label, case.getBatteryLevelCase(context))
|
||||||
|
setViewVisibility(R.id.pod_case_charging, if (case.isCaseCharging) View.VISIBLE else View.GONE)
|
||||||
|
}
|
||||||
|
|
||||||
// Right
|
// Right
|
||||||
setImageViewResource(R.id.pod_right_icon, device.rightPodIcon)
|
setImageViewResource(R.id.pod_right_icon, device.rightPodIcon)
|
||||||
setTextViewText(R.id.pod_right_label, getBatteryLevelRightPod(context))
|
setTextViewText(R.id.pod_right_label, getBatteryLevelRightPod(context))
|
||||||
|
val isRightPodCharging = (device as? HasChargeDetectionDual)?.isRightPodCharging ?: false
|
||||||
setViewVisibility(R.id.pod_right_charging, if (isRightPodCharging) View.VISIBLE else View.GONE)
|
setViewVisibility(R.id.pod_right_charging, if (isRightPodCharging) View.VISIBLE else View.GONE)
|
||||||
|
val isRightPodInEar = (device as? HasEarDetectionDual)?.isRightPodInEar ?: false
|
||||||
setViewVisibility(R.id.pod_right_ear, if (isRightPodInEar) View.VISIBLE else View.GONE)
|
setViewVisibility(R.id.pod_right_ear, if (isRightPodInEar) View.VISIBLE else View.GONE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSingleApplePods(device: SingleApplePods): RemoteViews = RemoteViews(
|
private fun createSinglePod(device: SinglePodDevice): RemoteViews = RemoteViews(
|
||||||
context.packageName,
|
context.packageName,
|
||||||
R.layout.monitor_notification_single_pods_small
|
R.layout.monitor_notification_single_pods_small
|
||||||
).apply {
|
).apply {
|
||||||
|
|||||||
@@ -6,14 +6,13 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.view.ContextThemeWrapper
|
import androidx.appcompat.view.ContextThemeWrapper
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
|
import androidx.core.view.isVisible
|
||||||
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.common.debug.autoreport.DebugSettings
|
import eu.darken.capod.common.debug.DebugSettings
|
||||||
import eu.darken.capod.databinding.PopupNotificationDualPodsBinding
|
import eu.darken.capod.databinding.PopupNotificationDualPodsBinding
|
||||||
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.DualApplePods
|
|
||||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
@@ -26,13 +25,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 DualPodDevice -> createDualPods(parent, device)
|
||||||
// Unused, has no case to trigger reaction?
|
// Unused, has no case to trigger reaction?
|
||||||
is SingleApplePods -> createSingleApplePods(parent, device)
|
is SinglePodDevice -> createSinglePod(parent, device)
|
||||||
else -> throw IllegalArgumentException("Unexpected device: $device")
|
else -> throw IllegalArgumentException("Unexpected device: $device")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDualApplePods(parent: ViewGroup, device: DualApplePods): View =
|
private fun createDualPods(parent: ViewGroup, device: DualPodDevice): View =
|
||||||
PopupNotificationDualPodsBinding.inflate(layoutInflater, parent, false).apply {
|
PopupNotificationDualPodsBinding.inflate(layoutInflater, parent, false).apply {
|
||||||
device.apply {
|
device.apply {
|
||||||
podIcon.setImageResource(iconRes)
|
podIcon.setImageResource(iconRes)
|
||||||
@@ -46,9 +45,12 @@ class PopUpPodViewFactory @Inject constructor(
|
|||||||
podLeftBatteryLabel.text = getBatteryLevelLeftPod(context)
|
podLeftBatteryLabel.text = getBatteryLevelLeftPod(context)
|
||||||
|
|
||||||
// Case
|
// Case
|
||||||
podCaseIcon.setImageResource(device.caseIcon)
|
podCaseContainer.isVisible = device is HasCase
|
||||||
podCaseBatteryIcon.setImageResource(getBatteryDrawable(batteryCasePercent))
|
(device as? HasCase)?.let { case ->
|
||||||
podCaseBatteryLabel.text = getBatteryLevelCase(context)
|
podCaseIcon.setImageResource(case.caseIcon)
|
||||||
|
podCaseBatteryIcon.setImageResource(getBatteryDrawable(case.batteryCasePercent))
|
||||||
|
podCaseBatteryLabel.text = case.getBatteryLevelCase(context)
|
||||||
|
}
|
||||||
|
|
||||||
// Right
|
// Right
|
||||||
podRightIcon.setImageResource(device.rightPodIcon)
|
podRightIcon.setImageResource(device.rightPodIcon)
|
||||||
@@ -57,7 +59,7 @@ class PopUpPodViewFactory @Inject constructor(
|
|||||||
}
|
}
|
||||||
}.root
|
}.root
|
||||||
|
|
||||||
private fun createSingleApplePods(parent: ViewGroup, device: SingleApplePods): View =
|
private fun createSinglePod(parent: ViewGroup, device: SinglePodDevice): View =
|
||||||
PopupNotificationSinglePodsBinding.inflate(layoutInflater, parent, false).apply {
|
PopupNotificationSinglePodsBinding.inflate(layoutInflater, parent, false).apply {
|
||||||
device.apply {
|
device.apply {
|
||||||
headphonesIcon.setImageResource(iconRes)
|
headphonesIcon.setImageResource(iconRes)
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
|
android:id="@+id/pod_case_container"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/pod_icon"
|
android:id="@+id/pod_icon"
|
||||||
style="@style/PodInfoItemIcon"
|
style="@style/PodInfoItemIcon.Popup"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/devic_earbuds_generic_both"
|
android:src="@drawable/devic_earbuds_generic_both"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/pod_label"
|
app:layout_constraintBottom_toBottomOf="@id/pod_label"
|
||||||
app:layout_constraintEnd_toStartOf="@id/pod_label"
|
app:layout_constraintEnd_toStartOf="@id/pod_label"
|
||||||
@@ -55,9 +53,7 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/pod_left_icon"
|
android:id="@+id/pod_left_icon"
|
||||||
style="@style/PodInfoItemIcon"
|
style="@style/PodInfoItemIcon.Popup"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:src="@drawable/devic_airpods_gen1_left"
|
android:src="@drawable/devic_airpods_gen1_left"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/pod_left_battery_label"
|
app:layout_constraintBottom_toBottomOf="@id/pod_left_battery_label"
|
||||||
@@ -67,9 +63,7 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@id/pod_left_battery_icon"
|
android:id="@id/pod_left_battery_icon"
|
||||||
style="@style/PodInfoItemIcon"
|
style="@style/PodInfoItemIcon.Popup"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/ic_baseline_battery_unknown_24"
|
android:src="@drawable/ic_baseline_battery_unknown_24"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/pod_left_battery_label"
|
app:layout_constraintBottom_toBottomOf="@id/pod_left_battery_label"
|
||||||
app:layout_constraintEnd_toStartOf="@id/pod_left_battery_label"
|
app:layout_constraintEnd_toStartOf="@id/pod_left_battery_label"
|
||||||
@@ -103,9 +97,7 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/pod_case_icon"
|
android:id="@+id/pod_case_icon"
|
||||||
style="@style/PodInfoItemIcon"
|
style="@style/PodInfoItemIcon.Popup"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:src="@drawable/devic_airpods_gen1_case"
|
android:src="@drawable/devic_airpods_gen1_case"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/pod_case_battery_label"
|
app:layout_constraintBottom_toBottomOf="@id/pod_case_battery_label"
|
||||||
@@ -115,9 +107,7 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@id/pod_case_battery_icon"
|
android:id="@id/pod_case_battery_icon"
|
||||||
style="@style/PodInfoItemIcon"
|
style="@style/PodInfoItemIcon.Popup"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/ic_baseline_battery_unknown_24"
|
android:src="@drawable/ic_baseline_battery_unknown_24"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/pod_case_battery_label"
|
app:layout_constraintBottom_toBottomOf="@id/pod_case_battery_label"
|
||||||
app:layout_constraintEnd_toStartOf="@id/pod_case_battery_label"
|
app:layout_constraintEnd_toStartOf="@id/pod_case_battery_label"
|
||||||
@@ -150,9 +140,7 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/pod_right_icon"
|
android:id="@+id/pod_right_icon"
|
||||||
style="@style/PodInfoItemIcon"
|
style="@style/PodInfoItemIcon.Popup"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:src="@drawable/devic_airpods_gen1_right"
|
android:src="@drawable/devic_airpods_gen1_right"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/pod_right_battery_label"
|
app:layout_constraintBottom_toBottomOf="@id/pod_right_battery_label"
|
||||||
@@ -162,9 +150,7 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@id/pod_right_battery_icon"
|
android:id="@id/pod_right_battery_icon"
|
||||||
style="@style/PodInfoItemIcon"
|
style="@style/PodInfoItemIcon.Popup"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/ic_baseline_battery_unknown_24"
|
android:src="@drawable/ic_baseline_battery_unknown_24"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/pod_right_battery_label"
|
app:layout_constraintBottom_toBottomOf="@id/pod_right_battery_label"
|
||||||
app:layout_constraintEnd_toStartOf="@id/pod_right_battery_label"
|
app:layout_constraintEnd_toStartOf="@id/pod_right_battery_label"
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/headphones_icon"
|
android:id="@+id/headphones_icon"
|
||||||
android:layout_width="wrap_content"
|
style="@style/PodInfoItemIcon.Popup"
|
||||||
style="@style/PodInfoItemIcon"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/devic_earbuds_generic_both"
|
android:src="@drawable/devic_earbuds_generic_both"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/headphones_label"
|
app:layout_constraintBottom_toBottomOf="@id/headphones_label"
|
||||||
app:layout_constraintEnd_toStartOf="@id/headphones_label"
|
app:layout_constraintEnd_toStartOf="@id/headphones_label"
|
||||||
@@ -56,9 +54,7 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@id/headphones_battery_icon"
|
android:id="@id/headphones_battery_icon"
|
||||||
android:layout_width="wrap_content"
|
style="@style/PodInfoItemIcon.Popup"
|
||||||
style="@style/PodInfoItemIcon"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/ic_baseline_battery_unknown_24"
|
android:src="@drawable/ic_baseline_battery_unknown_24"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/headphones_battery_label"
|
app:layout_constraintBottom_toBottomOf="@id/headphones_battery_label"
|
||||||
app:layout_constraintEnd_toStartOf="@id/headphones_battery_label"
|
app:layout_constraintEnd_toStartOf="@id/headphones_battery_label"
|
||||||
|
|||||||
@@ -38,6 +38,11 @@
|
|||||||
<item name="tint">?android:attr/colorControlNormal</item>
|
<item name="tint">?android:attr/colorControlNormal</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="PodInfoItemIcon.Popup">
|
||||||
|
<item name="android:layout_height">24dp</item>
|
||||||
|
<item name="android:layout_width">24dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="PodInfoItemText" parent="TextAppearance.MaterialComponents.Body2">
|
<style name="PodInfoItemText" parent="TextAppearance.MaterialComponents.Body2">
|
||||||
<item name="android:singleLine">true</item>
|
<item name="android:singleLine">true</item>
|
||||||
<item name="android:ellipsize">end</item>
|
<item name="android:ellipsize">end</item>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import com.android.build.gradle.LibraryExtension
|
import com.android.build.gradle.LibraryExtension
|
||||||
import org.gradle.api.Action
|
import org.gradle.api.Action
|
||||||
import org.gradle.api.JavaVersion
|
import org.gradle.api.JavaVersion
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.kotlin.dsl.apply
|
||||||
|
import org.gradle.kotlin.dsl.extra
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -122,4 +125,30 @@ fun getBugSnagApiKey(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return System.getenv("BUGSNAG_API_KEY") ?: bugsnagProps.getProperty("bugsnag.apikey")
|
return System.getenv("BUGSNAG_API_KEY") ?: bugsnagProps.getProperty("bugsnag.apikey")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use extra["useBugsnag"] = true per flavor to enable
|
||||||
|
*/
|
||||||
|
fun <T> T.setupBugsnagPlugin() where T : Project {
|
||||||
|
fun Project.`android`(configure: Action<com.android.build.gradle.internal.dsl.BaseAppModuleExtension>): Unit =
|
||||||
|
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("android", configure)
|
||||||
|
|
||||||
|
val key = "useBugsnag"
|
||||||
|
|
||||||
|
android {
|
||||||
|
productFlavors {
|
||||||
|
forEach {
|
||||||
|
val paramString = gradle.startParameter.taskRequests.toString().toLowerCase(Locale.ROOT)
|
||||||
|
val useBugsnag = if (it.extra.has(key)) it.extra[key] as Boolean else false
|
||||||
|
|
||||||
|
if (paramString.contains(it.name) && useBugsnag) {
|
||||||
|
println("Bugsnag plugin (com.bugsnag.android.gradle) applied to ${it.name} ($paramString)")
|
||||||
|
apply(plugin = "com.bugsnag.android.gradle")
|
||||||
|
} else {
|
||||||
|
println("Bugsnag plugin (com.bugsnag.android.gradle) NOT applied to ${it.name} ($paramString)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
### Updated by release.sh ###
|
### Updated by release.sh ###
|
||||||
project.versioning.major=2
|
project.versioning.major=2
|
||||||
project.versioning.minor=7
|
project.versioning.minor=8
|
||||||
project.versioning.patch=2
|
project.versioning.patch=0
|
||||||
project.versioning.build=0
|
project.versioning.build=0
|
||||||
#############################
|
#############################
|
||||||
|
|||||||
Reference in New Issue
Block a user