From 7b664bc6686f0a61faa4eae5aa4932f3ad070549 Mon Sep 17 00:00:00 2001 From: Andries Smit Date: Wed, 13 Jul 2022 14:51:56 +0200 Subject: [PATCH] feat: wear forward exceptions to phone --- app/src/main/AndroidManifest.xml | 4 + .../DataLayerListenerServiceMobile.kt | 42 +++++-- .../androidaps/utils/FabricPrivacy.kt | 4 + shared/src/main/res/values/wear_paths.xml | 3 +- wear/src/main/AndroidManifest.xml | 2 + .../java/info/nightscout/androidaps/Aaps.kt | 7 +- .../androidaps/comm/ExceptionService.kt | 110 ++++++++++++++++++ .../androidaps/comm/ExceptionWear.kt | 23 ++++ 8 files changed, 182 insertions(+), 13 deletions(-) create mode 100644 wear/src/main/java/info/nightscout/androidaps/comm/ExceptionService.kt create mode 100644 wear/src/main/java/info/nightscout/androidaps/comm/ExceptionWear.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8fb0dea2f6..89db45c95b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -208,6 +208,10 @@ android:host="*" android:pathPrefix="@string/path_rx_bridge" android:scheme="wear" /> + { - 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" } -} \ 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..7ec112ce9f 100644 --- a/core/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.kt +++ b/core/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.kt @@ -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") diff --git a/shared/src/main/res/values/wear_paths.xml b/shared/src/main/res/values/wear_paths.xml index 36d7325849..defc5544a9 100644 --- a/shared/src/main/res/values/wear_paths.xml +++ b/shared/src/main/res/values/wear_paths.xml @@ -1,4 +1,5 @@ /rx_bridge - \ No newline at end of file + /wear_error + diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml index 3d10810e37..dba8a05295 100644 --- a/wear/src/main/AndroidManifest.xml +++ b/wear/src/main/AndroidManifest.xml @@ -537,6 +537,8 @@ android:resource="@drawable/quick_wizard_tile_preview" /> + + = @@ -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)) } -} \ No newline at end of file +} diff --git a/wear/src/main/java/info/nightscout/androidaps/comm/ExceptionService.kt b/wear/src/main/java/info/nightscout/androidaps/comm/ExceptionService.kt new file mode 100644 index 0000000000..cc9964f142 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/comm/ExceptionService.kt @@ -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): 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) + } + } + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/comm/ExceptionWear.kt b/wear/src/main/java/info/nightscout/androidaps/comm/ExceptionWear.kt new file mode 100644 index 0000000000..1bbc18ab8b --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/comm/ExceptionWear.kt @@ -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) + } + +}