feat: wear forward exceptions to phone

This commit is contained in:
Andries Smit 2022-07-13 14:51:56 +02:00
parent 2b0670921e
commit 7b664bc668
8 changed files with 182 additions and 13 deletions

View file

@ -208,6 +208,10 @@
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

@ -8,18 +8,13 @@ import dagger.android.AndroidInjection
import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.events.EventMobileToWear
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.Loop
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.wear.WearPlugin
import info.nightscout.androidaps.plugins.general.wear.events.EventWearUpdateGui
import info.nightscout.androidaps.receivers.ReceiverStatusStore
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.wizard.QuickWizard
@ -31,6 +26,9 @@ 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() {
@ -64,6 +62,7 @@ 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)
@ -115,15 +114,40 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
if (wearPlugin.isEnabled()) {
when (messageEvent.path) {
rxPath -> {
aapsLogger.debug(LTag.WEAR, "onMessageReceived: ${String(messageEvent.data)}")
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() {
@ -203,4 +227,4 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
const val WEAR_CAPABILITY = "androidaps_wear"
}
}
}

View file

@ -77,6 +77,10 @@ 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")

View file

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

View file

@ -537,6 +537,8 @@
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,6 +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.di.DaggerWearComponent
import info.nightscout.androidaps.events.EventWearPreferenceChange
import info.nightscout.androidaps.plugins.bus.RxBus
@ -24,10 +25,10 @@ class Aaps : DaggerApplication(), OnSharedPreferenceChangeListener {
override fun onCreate() {
super.onCreate()
ExceptionWear(this)
aapsLogger.debug(LTag.WEAR, "onCreate")
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this)
startService(Intent(this, DataLayerListenerServiceWear::class.java))
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
@ -37,8 +38,8 @@ class Aaps : DaggerApplication(), OnSharedPreferenceChangeListener {
.build()
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
// we trigger update on Complications
// We trigger update on Complications
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(DataLayerListenerServiceWear.INTENT_NEW_DATA))
rxBus.send(EventWearPreferenceChange(key))
}
}
}

View file

@ -0,0 +1,110 @@
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

@ -0,0 +1,23 @@
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)
}
}