feat: wear forward exceptions to phone
This commit is contained in:
parent
2b0670921e
commit
7b664bc668
8 changed files with 182 additions and 13 deletions
|
@ -208,6 +208,10 @@
|
||||||
android:host="*"
|
android:host="*"
|
||||||
android:pathPrefix="@string/path_rx_bridge"
|
android:pathPrefix="@string/path_rx_bridge"
|
||||||
android:scheme="wear" />
|
android:scheme="wear" />
|
||||||
|
<data
|
||||||
|
android:host="*"
|
||||||
|
android:pathPrefix="@string/path_log_exception"
|
||||||
|
android:scheme="wear" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -8,18 +8,13 @@ import dagger.android.AndroidInjection
|
||||||
import info.nightscout.androidaps.R
|
import info.nightscout.androidaps.R
|
||||||
import info.nightscout.androidaps.database.AppRepository
|
import info.nightscout.androidaps.database.AppRepository
|
||||||
import info.nightscout.androidaps.events.EventMobileToWear
|
import info.nightscout.androidaps.events.EventMobileToWear
|
||||||
import info.nightscout.androidaps.interfaces.ActivePlugin
|
import info.nightscout.androidaps.interfaces.*
|
||||||
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.plugins.bus.RxBus
|
import info.nightscout.androidaps.plugins.bus.RxBus
|
||||||
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
|
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
|
||||||
import info.nightscout.androidaps.plugins.general.wear.WearPlugin
|
import info.nightscout.androidaps.plugins.general.wear.WearPlugin
|
||||||
import info.nightscout.androidaps.plugins.general.wear.events.EventWearUpdateGui
|
import info.nightscout.androidaps.plugins.general.wear.events.EventWearUpdateGui
|
||||||
import info.nightscout.androidaps.receivers.ReceiverStatusStore
|
import info.nightscout.androidaps.receivers.ReceiverStatusStore
|
||||||
import info.nightscout.androidaps.utils.DefaultValueHelper
|
import info.nightscout.androidaps.utils.DefaultValueHelper
|
||||||
import info.nightscout.androidaps.interfaces.ResourceHelper
|
|
||||||
import info.nightscout.androidaps.utils.FabricPrivacy
|
import info.nightscout.androidaps.utils.FabricPrivacy
|
||||||
import info.nightscout.androidaps.utils.rx.AapsSchedulers
|
import info.nightscout.androidaps.utils.rx.AapsSchedulers
|
||||||
import info.nightscout.androidaps.utils.wizard.QuickWizard
|
import info.nightscout.androidaps.utils.wizard.QuickWizard
|
||||||
|
@ -31,6 +26,9 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.tasks.await
|
import kotlinx.coroutines.tasks.await
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.ObjectInputStream
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class DataLayerListenerServiceMobile : WearableListenerService() {
|
class DataLayerListenerServiceMobile : WearableListenerService() {
|
||||||
|
@ -64,6 +62,7 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
|
||||||
private val disposable = CompositeDisposable()
|
private val disposable = CompositeDisposable()
|
||||||
|
|
||||||
private val rxPath get() = getString(R.string.path_rx_bridge)
|
private val rxPath get() = getString(R.string.path_rx_bridge)
|
||||||
|
private val exceptionPath get() = getString(R.string.path_log_exception)
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
AndroidInjection.inject(this)
|
AndroidInjection.inject(this)
|
||||||
|
@ -116,14 +115,39 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
|
||||||
if (wearPlugin.isEnabled()) {
|
if (wearPlugin.isEnabled()) {
|
||||||
when (messageEvent.path) {
|
when (messageEvent.path) {
|
||||||
rxPath -> {
|
rxPath -> {
|
||||||
aapsLogger.debug(LTag.WEAR, "onMessageReceived: ${String(messageEvent.data)}")
|
aapsLogger.debug(LTag.WEAR, "onMessageReceived rxPath: ${String(messageEvent.data)}")
|
||||||
val command = EventData.deserialize(String(messageEvent.data))
|
val command = EventData.deserialize(String(messageEvent.data))
|
||||||
rxBus.send(command.also { it.sourceNodeId = messageEvent.sourceNodeId })
|
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 var transcriptionNodeId: String? = null
|
||||||
|
|
||||||
private fun updateTranscriptionCapability() {
|
private fun updateTranscriptionCapability() {
|
||||||
|
|
|
@ -77,6 +77,10 @@ class FabricPrivacy @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getInstance(): FirebaseCrashlytics {
|
||||||
|
return FirebaseCrashlytics.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
// Crashlytics log message
|
// Crashlytics log message
|
||||||
fun logMessage(message: String) {
|
fun logMessage(message: String) {
|
||||||
aapsLogger.info(LTag.CORE,"Crashlytics log message: $message")
|
aapsLogger.info(LTag.CORE,"Crashlytics log message: $message")
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="path_rx_bridge" translatable="false">/rx_bridge</string>
|
<string name="path_rx_bridge" translatable="false">/rx_bridge</string>
|
||||||
|
<string name="path_log_exception" translatable="false">/wear_error</string>
|
||||||
</resources>
|
</resources>
|
|
@ -537,6 +537,8 @@
|
||||||
android:resource="@drawable/quick_wizard_tile_preview" />
|
android:resource="@drawable/quick_wizard_tile_preview" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service android:name=".comm.ExceptionService" android:process=":error"/>
|
||||||
|
|
||||||
<receiver android:name=".complications.ComplicationTapBroadcastReceiver" />
|
<receiver android:name=".complications.ComplicationTapBroadcastReceiver" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|
|
@ -9,6 +9,7 @@ import dagger.android.AndroidInjector
|
||||||
import dagger.android.DaggerApplication
|
import dagger.android.DaggerApplication
|
||||||
import info.nightscout.androidaps.comm.DataHandlerWear
|
import info.nightscout.androidaps.comm.DataHandlerWear
|
||||||
import info.nightscout.androidaps.comm.DataLayerListenerServiceWear
|
import info.nightscout.androidaps.comm.DataLayerListenerServiceWear
|
||||||
|
import info.nightscout.androidaps.comm.ExceptionWear
|
||||||
import info.nightscout.androidaps.di.DaggerWearComponent
|
import info.nightscout.androidaps.di.DaggerWearComponent
|
||||||
import info.nightscout.androidaps.events.EventWearPreferenceChange
|
import info.nightscout.androidaps.events.EventWearPreferenceChange
|
||||||
import info.nightscout.androidaps.plugins.bus.RxBus
|
import info.nightscout.androidaps.plugins.bus.RxBus
|
||||||
|
@ -24,10 +25,10 @@ class Aaps : DaggerApplication(), OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
ExceptionWear(this)
|
||||||
aapsLogger.debug(LTag.WEAR, "onCreate")
|
aapsLogger.debug(LTag.WEAR, "onCreate")
|
||||||
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this)
|
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this)
|
||||||
startService(Intent(this, DataLayerListenerServiceWear::class.java))
|
startService(Intent(this, DataLayerListenerServiceWear::class.java))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
|
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
|
||||||
|
@ -37,7 +38,7 @@ class Aaps : DaggerApplication(), OnSharedPreferenceChangeListener {
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
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))
|
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(DataLayerListenerServiceWear.INTENT_NEW_DATA))
|
||||||
rxBus.send(EventWearPreferenceChange(key))
|
rxBus.send(EventWearPreferenceChange(key))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue