mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Compare commits
2
Commits
ed66731207
...
43740d8b2e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43740d8b2e | ||
|
|
83849efe80 |
@@ -194,3 +194,14 @@ jobs:
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
# The Play flavor is obfuscated; the bundle embeds this mapping, but keep it reachable
|
||||
# for retracing user-submitted logs without going through Play Console.
|
||||
- name: Archive R8 mapping
|
||||
if: always() && !(github.event_name == 'workflow_dispatch' && inputs.dry_run)
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
||||
with:
|
||||
name: r8-mapping-${{ github.ref_name }}
|
||||
path: app/build/outputs/mapping/gplay*/mapping.txt
|
||||
if-no-files-found: warn
|
||||
overwrite: true
|
||||
|
||||
@@ -55,10 +55,12 @@ android {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
}
|
||||
proguardFiles("proguard-rules-foss.pro")
|
||||
}
|
||||
create("gplay") {
|
||||
dimension = "version"
|
||||
signingConfig = signingConfigs["releaseGplay"]
|
||||
proguardFiles("proguard-rules-gplay.pro")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# The FOSS build is open source and ships on GitHub/F-Droid; readable stack traces are worth more
|
||||
# than smaller identifiers there. Only the Google Play flavor obfuscates (proguard-rules-gplay.pro).
|
||||
-dontobfuscate
|
||||
@@ -0,0 +1,21 @@
|
||||
# Google Play scores the obfuscation share of every uploaded bundle and restricts listings that
|
||||
# stay below its threshold, so the Play flavor obfuscates. The FOSS flavor does not
|
||||
# (proguard-rules-foss.pro).
|
||||
|
||||
# Keep stack traces retraceable with the mapping file that the bundle embeds.
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-renamesourcefileattribute SourceFile
|
||||
|
||||
# AAP session logs identify settings and commands by their simple class name
|
||||
# (AapSessionEngine, AapAncController, AapOutboundController). Names only; unused classes are
|
||||
# still removed.
|
||||
-keepnames class eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
|
||||
-keepnames class * extends eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
|
||||
-keepnames class eu.darken.capod.pods.core.apple.aap.protocol.AapCommand
|
||||
-keepnames class * extends eu.darken.capod.pods.core.apple.aap.protocol.AapCommand
|
||||
|
||||
# ViewModel1 derives its log tag from the subclass name (VM:OverviewViewModel).
|
||||
-keepnames class * extends eu.darken.capod.common.uix.ViewModel1
|
||||
|
||||
# Error dialogs and log summaries show exceptions by class name (LocalizedError, asLogSummary).
|
||||
-keepnames class * extends java.lang.Throwable
|
||||
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
-keep class eu.darken.capod.BuildConfig { *; }
|
||||
-dontobfuscate
|
||||
|
||||
# work-runtime 2.7.1 (pulled by Glance) uses Class.newInstance() reflection throughout.
|
||||
# R8 full mode strips no-arg constructors not reachable by static analysis.
|
||||
|
||||
+3
-1
@@ -10,6 +10,7 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.error.HasLocalizedError
|
||||
import eu.darken.capod.common.error.LocalizedError
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
|
||||
class GplayServiceUnavailableException(cause: Throwable) :
|
||||
BillingException("Google Play services are unavailable.", cause), HasLocalizedError {
|
||||
@@ -48,10 +49,11 @@ class GplayServiceUnavailableException(cause: Throwable) :
|
||||
)
|
||||
|
||||
private fun onLaunchFailed(e: Exception) {
|
||||
log(ERROR) { "Can't launch settings intent for Google Play: $e" }
|
||||
log(TAG, ERROR) { "Can't launch settings intent for Google Play: $e" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val GPLAY_PKG = "com.android.vending"
|
||||
private val TAG = logTag("Upgrade", "Gplay", "ServiceUnavailable")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import android.content.BroadcastReceiver
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
|
||||
fun BroadcastReceiver.PendingResult.finish2(): Boolean = try {
|
||||
finish()
|
||||
true
|
||||
} catch (e: IllegalStateException) {
|
||||
log { "BroadcastReceiver.PendingResult.finish() failed: $e" }
|
||||
log(TAG) { "BroadcastReceiver.PendingResult.finish() failed: $e" }
|
||||
false
|
||||
}
|
||||
|
||||
private val TAG = logTag("Common", "BroadcastReceiver")
|
||||
|
||||
@@ -9,6 +9,7 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import javax.inject.Inject
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
|
||||
@Reusable
|
||||
class WebpageTool @Inject constructor(
|
||||
@@ -25,9 +26,12 @@ class WebpageTool @Inject constructor(
|
||||
context.startActivity(intent)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
log(ERROR) { "Failed to launch: ${e.asLog()}" }
|
||||
log(TAG, ERROR) { "Failed to launch: ${e.asLog()}" }
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("WebpageTool")
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package eu.darken.capod.common.bluetooth
|
||||
|
||||
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.logTag
|
||||
|
||||
suspend fun Collection<BleScanResult>.onlyNewAndUnique(): List<BleScanResult> = this
|
||||
.groupBy { it.address }
|
||||
@@ -10,7 +11,9 @@ suspend fun Collection<BleScanResult>.onlyNewAndUnique(): List<BleScanResult> =
|
||||
// For each address we only want the newest result, upstream may batch data
|
||||
val newest = sameAdrDevs.maxByOrNull { it.generatedAtNanos }!!
|
||||
sameAdrDevs.minus(newest).let {
|
||||
if (it.isNotEmpty()) log(VERBOSE) { "Discarding stale results: ${it.logSummary()}" }
|
||||
if (it.isNotEmpty()) log(TAG, VERBOSE) { "Discarding stale results: ${it.logSummary()}" }
|
||||
}
|
||||
newest
|
||||
}
|
||||
|
||||
private val TAG = logTag("Bluetooth", "Scanner")
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.bluetooth.le.ScanResult
|
||||
import android.os.ParcelUuid
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
|
||||
fun BluetoothDevice.hasFeature(uuid: ParcelUuid): Boolean {
|
||||
return uuids?.contains(uuid) ?: false
|
||||
@@ -21,6 +22,8 @@ fun BluetoothDevice.hasFeature(uuid: ParcelUuid): Boolean {
|
||||
fun ScanFilter.matchesSafe(scanResult: ScanResult): Boolean = try {
|
||||
matches(scanResult)
|
||||
} catch (e: NullPointerException) {
|
||||
log { "AOSP error: ${e.asLog()}" }
|
||||
log(TAG) { "AOSP error: ${e.asLog()}" }
|
||||
false
|
||||
}
|
||||
|
||||
private val TAG = logTag("Bluetooth", "Extensions")
|
||||
|
||||
@@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
|
||||
class DataStoreValue<T>(
|
||||
private val dataStore: DataStore<Preferences>,
|
||||
@@ -45,10 +46,14 @@ class DataStoreValue<T>(
|
||||
prefs[key as Preferences.Key<Any>] = raw
|
||||
}
|
||||
}
|
||||
log(VERBOSE) { "DataStoreValue($keyName) updated from $old to $new" }
|
||||
log(TAG, VERBOSE) { "DataStoreValue($keyName) updated from $old to $new" }
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return Updated(old as T, new as T)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("DataStore", "Value")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> DataStoreValue<T>.value(): T = flow.first()
|
||||
|
||||
@@ -34,6 +34,8 @@ object Logging {
|
||||
)
|
||||
}
|
||||
|
||||
private val TAG = logTag("Logging")
|
||||
|
||||
private val internalLoggers = mutableListOf<Logger>()
|
||||
|
||||
val loggers: List<Logger>
|
||||
@@ -46,11 +48,11 @@ object Logging {
|
||||
|
||||
fun install(logger: Logger) {
|
||||
synchronized(internalLoggers) { internalLoggers.add(logger) }
|
||||
log { "Was installed $logger" }
|
||||
log(TAG) { "Was installed $logger" }
|
||||
}
|
||||
|
||||
fun remove(logger: Logger) {
|
||||
log { "Removing: $logger" }
|
||||
log(TAG) { "Removing: $logger" }
|
||||
synchronized(internalLoggers) { internalLoggers.remove(logger) }
|
||||
}
|
||||
|
||||
@@ -77,26 +79,11 @@ object Logging {
|
||||
}
|
||||
|
||||
fun clearAll() {
|
||||
log { "Clearing all loggers" }
|
||||
log(TAG) { "Clearing all loggers" }
|
||||
synchronized(internalLoggers) { internalLoggers.clear() }
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Any.log(
|
||||
priority: Logging.Priority = Logging.Priority.DEBUG,
|
||||
metaData: Map<String, Any>? = null,
|
||||
message: () -> String,
|
||||
) {
|
||||
if (Logging.hasReceivers) {
|
||||
Logging.logInternal(
|
||||
tag = "CAP:${logTagViaCallSite()}",
|
||||
priority = priority,
|
||||
metaData = metaData,
|
||||
message = message(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun log(
|
||||
tag: String,
|
||||
priority: Logging.Priority = Logging.Priority.DEBUG,
|
||||
@@ -135,15 +122,3 @@ fun Throwable.asLogSummary(): String {
|
||||
|
||||
private fun Throwable.safeMessage(): String? = runCatching { message }.getOrNull()
|
||||
|
||||
@PublishedApi
|
||||
internal fun Any.logTagViaCallSite(): String {
|
||||
val javaClass = this::class.java
|
||||
val fullClassName = javaClass.name
|
||||
val outerClassName = fullClassName.substringBefore('$')
|
||||
val simplerOuterClassName = outerClassName.substringAfterLast('.')
|
||||
return if (simplerOuterClassName.isEmpty()) {
|
||||
fullClassName
|
||||
} else {
|
||||
simplerOuterClassName.removeSuffix("Kt")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.lifecycle.Observer
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
|
||||
/**
|
||||
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
|
||||
@@ -46,7 +47,7 @@ class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||
@MainThread
|
||||
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
|
||||
if (hasActiveObservers()) {
|
||||
log(WARN) { "Multiple observers registered but only one will be notified of changes." }
|
||||
log(TAG, WARN) { "Multiple observers registered but only one will be notified of changes." }
|
||||
}
|
||||
|
||||
// Observe the internal MutableLiveData
|
||||
@@ -73,4 +74,8 @@ class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||
fun call() {
|
||||
value = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("SingleLiveEvent")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ abstract class ViewModel2(
|
||||
|
||||
if (this is ErrorEventSource) {
|
||||
return CoroutineExceptionHandler { _, ex ->
|
||||
log(WARN) { "Error during launch: ${ex.asLog()}" }
|
||||
log(TAG, WARN) { "Error during launch: ${ex.asLog()}" }
|
||||
errorEvents.postValue(ex)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class BluetoothEventReceiver : BroadcastReceiver() {
|
||||
log(TAG, WARN) { "Event without Bluetooth device association." }
|
||||
return
|
||||
} else {
|
||||
log { "Event related to $bluetoothDevice" }
|
||||
log(TAG) { "Event related to $bluetoothDevice" }
|
||||
}
|
||||
val supportedFeatures = try {
|
||||
ContinuityProtocol.BLE_FEATURE_UUIDS.filter { bluetoothDevice.hasFeature(it) }
|
||||
@@ -47,7 +47,7 @@ class BluetoothEventReceiver : BroadcastReceiver() {
|
||||
log(TAG) { "Device has no features we support." }
|
||||
return
|
||||
} else {
|
||||
log { "Device has the following we features we support $supportedFeatures" }
|
||||
log(TAG) { "Device has the following we features we support $supportedFeatures" }
|
||||
}
|
||||
|
||||
when (intent.action) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import eu.darken.capod.pods.core.apple.ble.BATTERY_RESOLUTION_DECILE
|
||||
import eu.darken.capod.pods.core.apple.ble.BATTERY_RESOLUTION_PERCENT
|
||||
import eu.darken.capod.pods.core.apple.ble.DualBlePodSnapshot
|
||||
import eu.darken.capod.pods.core.apple.ble.DualBlePodSnapshot.Pod
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
|
||||
interface DualApplePods : ApplePods, HasChargeDetectionDual, DualBlePodSnapshot, HasEarDetectionDual, HasCase,
|
||||
HasDualMicrophone, HasAppleColor {
|
||||
@@ -38,7 +39,7 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualBlePodSnapshot,
|
||||
return when (value) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Left pod: Above 100% battery: $value" }
|
||||
log(TAG) { "Left pod: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
(value / 10f)
|
||||
@@ -59,7 +60,7 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualBlePodSnapshot,
|
||||
return when (value) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Right pod: Above 100% battery: $value" }
|
||||
log(TAG) { "Right pod: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
@@ -131,7 +132,7 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualBlePodSnapshot,
|
||||
return when (val value = pubCaseBattery.toInt()) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Case: Above 100% battery: $value" }
|
||||
log(TAG) { "Case: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
@@ -206,3 +207,5 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualBlePodSnapshot,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private val TAG = logTag("Pod", "Apple", "DualApplePods")
|
||||
|
||||
@@ -3,6 +3,7 @@ package eu.darken.capod.pods.core.apple.ble.devices
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.pods.core.apple.ble.SingleBlePodSnapshot
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
|
||||
/**
|
||||
* Devices that only present a single charge level, e.g. most Beats devices
|
||||
@@ -17,7 +18,7 @@ interface SingleApplePods : ApplePods, SingleBlePodSnapshot, HasAppleColor {
|
||||
return when (val value = pubPodsBattery.lowerNibble.toInt()) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Headset above 100% battery: $value" }
|
||||
log(TAG) { "Headset above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
(value / 10f)
|
||||
@@ -25,3 +26,5 @@ interface SingleApplePods : ApplePods, SingleBlePodSnapshot, HasAppleColor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val TAG = logTag("Pod", "Apple", "SingleApplePods")
|
||||
|
||||
@@ -14,7 +14,7 @@ object ProximityPairing {
|
||||
class Decoder @Inject constructor() {
|
||||
fun decode(message: ContinuityProtocol.Message): ProximityMessage? {
|
||||
if (message.type != CONTINUITY_PROTOCOL_MESSAGE_TYPE_PROXIMITY_PAIRING) {
|
||||
log { "Not a proximity pairing message: $this" }
|
||||
log(TAG) { "Not a proximity pairing message: $message" }
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ class DeviceProfilesRepo @Inject constructor(
|
||||
if (addFirst) add(0, profile) else add(profile)
|
||||
}.toList()
|
||||
settings.profiles.valueBlocking = DeviceProfilesContainer(updatedProfiles)
|
||||
log(VERBOSE) { "Added device profile: ${profile.label}" }
|
||||
log(TAG, VERBOSE) { "Added device profile: ${profile.label}" }
|
||||
}
|
||||
|
||||
suspend fun updateProfile(profile: DeviceProfile) = mutex.withLock {
|
||||
@@ -152,7 +152,7 @@ class DeviceProfilesRepo @Inject constructor(
|
||||
if (it.id == profile.id) profile else it
|
||||
}
|
||||
settings.profiles.valueBlocking = DeviceProfilesContainer(updatedProfiles)
|
||||
log(VERBOSE) { "Updated device profile: ${profile.label}" }
|
||||
log(TAG, VERBOSE) { "Updated device profile: ${profile.label}" }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,14 +176,14 @@ class DeviceProfilesRepo @Inject constructor(
|
||||
checkAddressUniqueness(updated, otherProfiles)
|
||||
val updatedProfiles = currentContainer.profiles.map { if (it.id == id) updated else it }
|
||||
settings.profiles.valueBlocking = DeviceProfilesContainer(updatedProfiles)
|
||||
log(VERBOSE) { "Updated apple device profile: ${updated.label}" }
|
||||
log(TAG, VERBOSE) { "Updated apple device profile: ${updated.label}" }
|
||||
}
|
||||
|
||||
suspend fun removeProfile(profileId: ProfileId) = mutex.withLock {
|
||||
val currentContainer = settings.profiles.valueBlocking
|
||||
val updatedProfiles = currentContainer.profiles.filter { it.id != profileId }
|
||||
settings.profiles.valueBlocking = DeviceProfilesContainer(updatedProfiles)
|
||||
log(VERBOSE) { "Removed device profile with ID: $profileId" }
|
||||
log(TAG, VERBOSE) { "Removed device profile with ID: $profileId" }
|
||||
deviceStateCache.delete(profileId)
|
||||
batteryDrainStore.delete(profileId)
|
||||
}
|
||||
@@ -196,7 +196,7 @@ class DeviceProfilesRepo @Inject constructor(
|
||||
val byId = current.associateBy { it.id }
|
||||
val reordered = orderedIds.map { byId.getValue(it) }
|
||||
settings.profiles.valueBlocking = DeviceProfilesContainer(reordered)
|
||||
log(VERBOSE) { "Reordered ${reordered.size} device profiles by ID" }
|
||||
log(TAG, VERBOSE) { "Reordered ${reordered.size} device profiles by ID" }
|
||||
}
|
||||
|
||||
suspend fun clear() = mutex.withLock {
|
||||
|
||||
Reference in New Issue
Block a user