diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataHandlerMobile.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataHandlerMobile.kt index 2cb74131ad..64623dbf12 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataHandlerMobile.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataHandlerMobile.kt @@ -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() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataLayerListenerServiceMobile.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataLayerListenerServiceMobile.kt index 4535cda62f..4292307c59 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataLayerListenerServiceMobile.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataLayerListenerServiceMobile.kt @@ -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 @@ -116,7 +111,7 @@ class DataLayerListenerServiceMobile : WearableListenerService() { if (wearPlugin.isEnabled()) { when (messageEvent.path) { 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)) rxBus.send(command.also { it.sourceNodeId = messageEvent.sourceNodeId }) } @@ -203,4 +198,4 @@ class DataLayerListenerServiceMobile : WearableListenerService() { const val WEAR_CAPABILITY = "androidaps_wear" } -} \ No newline at end of file +} diff --git a/core/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.kt b/core/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.kt index e4ed8f77fa..46a8813120 100644 --- a/core/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.kt +++ b/core/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.kt @@ -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 @@ -79,7 +83,7 @@ class FabricPrivacy @Inject constructor( // 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) } @@ -92,4 +96,31 @@ class FabricPrivacy @Inject constructor( fun fabricEnabled(): Boolean { return sp.getBoolean(R.string.key_enable_fabric, true) } -} \ No newline at end of file + + 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") + } +} diff --git a/shared/src/main/java/info/nightscout/shared/weardata/EventData.kt b/shared/src/main/java/info/nightscout/shared/weardata/EventData.kt index c2d38b1d4d..ec450b91a8 100644 --- a/shared/src/main/java/info/nightscout/shared/weardata/EventData.kt +++ b/shared/src/main/java/info/nightscout/shared/weardata/EventData.kt @@ -25,6 +25,29 @@ 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() { + + override fun equals(other: Any?): Boolean = + when (other) { + !is WearException -> false + else -> timeStamp == other.timeStamp && fingerprint == other.fingerprint + } + + override fun hashCode(): Int { + return Objects.hash(timeStamp, fingerprint) + } + } + @Serializable data class Error(val timeStamp: Long) : EventData() // ignored diff --git a/wear/src/main/java/info/nightscout/androidaps/Aaps.kt b/wear/src/main/java/info/nightscout/androidaps/Aaps.kt index ef5ca19d11..1cb262ef6a 100644 --- a/wear/src/main/java/info/nightscout/androidaps/Aaps.kt +++ b/wear/src/main/java/info/nightscout/androidaps/Aaps.kt @@ -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.ExceptionHandlerWear import info.nightscout.androidaps.di.DaggerWearComponent import info.nightscout.androidaps.events.EventWearPreferenceChange import info.nightscout.androidaps.plugins.bus.RxBus @@ -21,13 +22,14 @@ 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() + exceptionHandlerWear.register() aapsLogger.debug(LTag.WEAR, "onCreate") PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this) startService(Intent(this, DataLayerListenerServiceWear::class.java)) - } override fun applicationInjector(): AndroidInjector = @@ -37,8 +39,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)) } -} \ No newline at end of file +} diff --git a/wear/src/main/java/info/nightscout/androidaps/comm/ExceptionHandlerWear.kt b/wear/src/main/java/info/nightscout/androidaps/comm/ExceptionHandlerWear.kt new file mode 100644 index 0000000000..4b6efdc21f --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/comm/ExceptionHandlerWear.kt @@ -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() + } + +}