chore: wear exception handling via rx bridge
This commit is contained in:
parent
7b664bc668
commit
5eec69d863
11 changed files with 129 additions and 181 deletions
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
|
@ -119,35 +115,10 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
|
|||
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() {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue