chore: wear exception handling via rx bridge

This commit is contained in:
Andries Smit 2022-07-13 22:13:06 +02:00
parent 7b664bc668
commit 5eec69d863
11 changed files with 129 additions and 181 deletions

View file

@ -208,10 +208,6 @@
android:host="*"
android:pathPrefix="@string/path_rx_bridge"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_log_exception"
android:scheme="wear" />
</intent-filter>
</service>
<service

View file

@ -271,6 +271,13 @@ class DataHandlerMobile @Inject constructor(
aapsLogger.debug(LTag.WEAR, "SnoozeAlert received $it from ${it.sourceNodeId}")
alarmSoundServiceHelper.stopService(context, "Muted from wear")
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventData.WearException::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
aapsLogger.debug(LTag.WEAR, "WearException received $it from ${it.sourceNodeId}")
fabricPrivacy.logWearException(it)
}, fabricPrivacy::logException)
}
private fun handleTddStatus() {

View file

@ -26,9 +26,6 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import kotlinx.coroutines.*
import kotlinx.coroutines.tasks.await
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.ObjectInputStream
import javax.inject.Inject
class DataLayerListenerServiceMobile : WearableListenerService() {
@ -62,7 +59,6 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
private val disposable = CompositeDisposable()
private val rxPath get() = getString(R.string.path_rx_bridge)
private val exceptionPath get() = getString(R.string.path_log_exception)
override fun onCreate() {
AndroidInjection.inject(this)
@ -114,40 +110,15 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
if (wearPlugin.isEnabled()) {
when (messageEvent.path) {
rxPath -> {
rxPath -> {
aapsLogger.debug(LTag.WEAR, "onMessageReceived rxPath: ${String(messageEvent.data)}")
val command = EventData.deserialize(String(messageEvent.data))
rxBus.send(command.also { it.sourceNodeId = messageEvent.sourceNodeId })
}
exceptionPath -> logWearException(messageEvent)
}
}
}
private fun logWearException(messageEvent: MessageEvent) {
aapsLogger.debug(LTag.WEAR, "logWearException")
val map = DataMap.fromByteArray(messageEvent.data)
val bis = ByteArrayInputStream(map.getByteArray("exception"))
try {
val ois = ObjectInputStream(bis)
fabricPrivacy.getInstance().apply {
setCustomKey("wear_exception", true)
setCustomKey("wear_board", map.getString("board") ?: "unknown")
setCustomKey("wear_fingerprint", map.getString("fingerprint") ?: "unknown")
setCustomKey("wear_sdk", map.getString("sdk") ?: "unknown")
setCustomKey("wear_model", map.getString("model") ?: "unknown")
setCustomKey("wear_manufacturer", map.getString("manufacturer") ?: "unknown")
setCustomKey("wear_product", map.getString("product") ?: "unknown")
}
fabricPrivacy.logException(ois.readObject() as Throwable)
} catch (e: IOException) {
e.printStackTrace()
} catch (e: ClassNotFoundException) {
e.printStackTrace()
}
}
private var transcriptionNodeId: String? = null
private fun updateTranscriptionCapability() {

View file

@ -10,6 +10,10 @@ import info.nightscout.androidaps.core.R
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.weardata.EventData
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.ObjectInputStream
import javax.inject.Inject
import javax.inject.Singleton
@ -77,13 +81,9 @@ class FabricPrivacy @Inject constructor(
}
}
fun getInstance(): FirebaseCrashlytics {
return FirebaseCrashlytics.getInstance()
}
// Crashlytics log message
fun logMessage(message: String) {
aapsLogger.info(LTag.CORE,"Crashlytics log message: $message")
aapsLogger.info(LTag.CORE, "Crashlytics log message: $message")
FirebaseCrashlytics.getInstance().log(message)
}
@ -96,4 +96,31 @@ class FabricPrivacy @Inject constructor(
fun fabricEnabled(): Boolean {
return sp.getBoolean(R.string.key_enable_fabric, true)
}
}
fun logWearException(wearException: EventData.WearException) {
aapsLogger.debug(LTag.WEAR, "logWearException")
FirebaseCrashlytics.getInstance().apply {
setCustomKey("wear_exception", true)
setCustomKey("wear_board", wearException.board)
setCustomKey("wear_fingerprint", wearException.fingerprint)
setCustomKey("wear_sdk", wearException.sdk)
setCustomKey("wear_model", wearException.model)
setCustomKey("wear_manufacturer", wearException.manufacturer)
setCustomKey("wear_product", wearException.product)
}
logException(byteArrayToThrowable(wearException.exception))
}
private fun byteArrayToThrowable(wearExceptionData: ByteArray): Throwable {
val bis = ByteArrayInputStream(wearExceptionData)
try {
val ois = ObjectInputStream(bis)
return ois.readObject() as Throwable
} catch (e: IOException) {
e.printStackTrace()
} catch (e: ClassNotFoundException) {
e.printStackTrace()
}
return IllegalArgumentException("Wear Exception could not be de-serialized")
}
}

View file

@ -25,6 +25,18 @@ sealed class EventData : Event() {
@Serializable
data class ActionPong(val timeStamp: Long, val apiLevel: Int) : EventData()
@Serializable
data class WearException(
val timeStamp: Long,
val exception: ByteArray,
val board: String,
val fingerprint: String,
val sdk: String,
val model: String,
val manufacturer: String,
val product: String
) : EventData()
@Serializable
data class Error(val timeStamp: Long) : EventData() // ignored
@ -134,9 +146,9 @@ sealed class EventData : Event() {
override fun equals(other: Any?): Boolean =
when {
other !is SingleBg -> false
other !is SingleBg -> false
color != other.color -> false
else -> timeStamp == other.timeStamp
else -> timeStamp == other.timeStamp
}
override fun hashCode(): Int {

View file

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="path_rx_bridge" translatable="false">/rx_bridge</string>
<string name="path_log_exception" translatable="false">/wear_error</string>
</resources>
</resources>

View file

@ -537,8 +537,6 @@
android:resource="@drawable/quick_wizard_tile_preview" />
</service>
<service android:name=".comm.ExceptionService" android:process=":error"/>
<receiver android:name=".complications.ComplicationTapBroadcastReceiver" />
<activity

View file

@ -9,7 +9,7 @@ import dagger.android.AndroidInjector
import dagger.android.DaggerApplication
import info.nightscout.androidaps.comm.DataHandlerWear
import info.nightscout.androidaps.comm.DataLayerListenerServiceWear
import info.nightscout.androidaps.comm.ExceptionWear
import info.nightscout.androidaps.comm.ExceptionHandlerWear
import info.nightscout.androidaps.di.DaggerWearComponent
import info.nightscout.androidaps.events.EventWearPreferenceChange
import info.nightscout.androidaps.plugins.bus.RxBus
@ -22,10 +22,11 @@ class Aaps : DaggerApplication(), OnSharedPreferenceChangeListener {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var rxBus: RxBus
@Inject lateinit var dataHandlerWear: DataHandlerWear // instantiate only
@Inject lateinit var exceptionHandlerWear: ExceptionHandlerWear
override fun onCreate() {
super.onCreate()
ExceptionWear(this)
exceptionHandlerWear.register()
aapsLogger.debug(LTag.WEAR, "onCreate")
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this)
startService(Intent(this, DataLayerListenerServiceWear::class.java))

View file

@ -0,0 +1,70 @@
package info.nightscout.androidaps.comm
import android.os.Build
import android.util.Log
import info.nightscout.androidaps.events.EventWearToMobile
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.shared.weardata.EventData
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.ObjectOutputStream
import javax.inject.Inject
class ExceptionHandlerWear @Inject constructor(
private val rxBus: RxBus,
) {
private var mDefaultUEH: Thread.UncaughtExceptionHandler? = null
private val mWearUEH = Thread.UncaughtExceptionHandler { thread, ex ->
Log.d("WEAR", "uncaughtException :" + ex.message)
// Pass the exception to the bus which will send the data upstream to your Smartphone/Tablet
val wearException = EventData.WearException(
timeStamp = System.currentTimeMillis(),
exception = exceptionToByteArray(ex),
board = Build.BOARD,
sdk = Build.VERSION.SDK_INT.toString(),
fingerprint = Build.FINGERPRINT,
model = Build.MODEL,
manufacturer = Build.MANUFACTURER,
product = Build.PRODUCT
)
rxBus.send(EventWearToMobile(wearException))
// Let the default UncaughtExceptionHandler take it from here
mDefaultUEH?.uncaughtException(thread, ex)
}
fun register() {
mDefaultUEH = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler(mWearUEH)
}
private fun exceptionToByteArray(ex: Throwable): ByteArray {
ex.stackTrace // Make sure the stacktrace gets built up
val bos = ByteArrayOutputStream()
var oos: ObjectOutputStream? = null
try {
oos = ObjectOutputStream(bos)
oos.writeObject(ex)
return bos.toByteArray()
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
oos?.close()
} catch (exx: IOException) {
// Ignore close exception
}
try {
bos.close()
} catch (exx: IOException) {
// Ignore close exception
}
}
return byteArrayOf()
}
}

View file

@ -1,110 +0,0 @@
package info.nightscout.androidaps.comm
import android.app.IntentService
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import android.util.Log
import com.google.android.gms.tasks.Tasks
import com.google.android.gms.wearable.CapabilityClient
import com.google.android.gms.wearable.CapabilityInfo
import com.google.android.gms.wearable.DataMap
import com.google.android.gms.wearable.Node
import com.google.android.gms.wearable.Wearable
import info.nightscout.androidaps.R
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.ObjectOutputStream
class ExceptionService : IntentService(ExceptionService::class.simpleName) {
private val messageClient by lazy { Wearable.getMessageClient(this) }
private val capabilityClient by lazy { Wearable.getCapabilityClient(this) }
private var handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
private val exceptionPath get() = getString(R.string.path_log_exception)
private var transcriptionNodeId: String? = null
override fun onCreate() {
super.onCreate()
handler.post { updateTranscriptionCapability() }
}
private fun updateTranscriptionCapability() {
val capabilityInfo: CapabilityInfo = Tasks.await(
capabilityClient.getCapability(DataLayerListenerServiceWear.PHONE_CAPABILITY, CapabilityClient.FILTER_REACHABLE)
)
Log.d("WEAR", "Nodes: ${capabilityInfo.nodes.joinToString(", ") { it.displayName + "(" + it.id + ")" }}")
pickBestNodeId(capabilityInfo.nodes)?.let { transcriptionNodeId = it }
Log.d("WEAR", "Selected node: $transcriptionNodeId")
}
// Find a nearby node or pick one arbitrarily
private fun pickBestNodeId(nodes: Set<Node>): String? =
nodes.firstOrNull { it.isNearby }?.id ?: nodes.firstOrNull()?.id
private fun sendMessage(path: String, data: ByteArray) {
transcriptionNodeId?.also { nodeId ->
messageClient
.sendMessage(nodeId, path, data).apply {
addOnSuccessListener { }
addOnFailureListener {
Log.d("WEAR", "sendMessage: $path failure")
}
}
}
}
override fun onHandleIntent(intent: Intent?) {
Log.d("WEAR", "onHandleIntent: ErrorService $intent")
val bos = ByteArrayOutputStream()
var oos: ObjectOutputStream? = null
try {
oos = ObjectOutputStream(bos)
oos.writeObject(intent?.getSerializableExtra("exception"))
val exceptionData = bos.toByteArray()
val dataMap = DataMap()
dataMap.putString("board", Build.BOARD)
dataMap.putString("sdk", Build.VERSION.SDK_INT.toString())
dataMap.putString("fingerprint", Build.FINGERPRINT)
dataMap.putString("model", Build.MODEL)
dataMap.putString("manufacturer", Build.MANUFACTURER)
dataMap.putString("product", Build.PRODUCT)
dataMap.putByteArray("exception", exceptionData)
handler.post {
sendMessage(exceptionPath, dataMap.toByteArray())
}
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
oos?.close()
} catch (exx: IOException) {
// ignore close exception
}
try {
bos.close()
} catch (exx: IOException) {
// ignore close exception
}
}
}
init {
setIntentRedelivery(true)
}
companion object {
fun reportException(context: Context, ex: Throwable?) {
val errorIntent = Intent(context, ExceptionService::class.java)
errorIntent.putExtra("exception", ex)
context.startService(errorIntent)
}
}
}

View file

@ -1,23 +0,0 @@
package info.nightscout.androidaps.comm
import android.content.Context
import android.util.Log
class ExceptionWear(private val context: Context) {
private var mDefaultUEH: Thread.UncaughtExceptionHandler? = null
private val mWearUEH = Thread.UncaughtExceptionHandler { thread, ex ->
Log.d("WEAR", "uncaughtException :" + ex.message)
// Pass the exception to a Service which will send the data upstream to your Smartphone/Tablet
ExceptionService.reportException(context, ex)
// Let the default UncaughtExceptionHandler take it from here
mDefaultUEH?.uncaughtException(thread, ex)
}
init {
mDefaultUEH = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler(mWearUEH)
}
}