diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3712e15ad..c708341f8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,4 +35,12 @@ + + + + + + + + diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/messages/HealthSummaryService.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/messages/HealthSummaryService.kt index ed84519b6..988df5b6f 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/messages/HealthSummaryService.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/messages/HealthSummaryService.kt @@ -2,9 +2,9 @@ package edu.stanford.bdh.engagehf.messages import android.content.Context import android.content.Intent -import android.os.Environment import androidx.core.content.FileProvider import dagger.hilt.android.qualifiers.ApplicationContext +import edu.stanford.bdh.engagehf.R import edu.stanford.spezi.core.coroutines.di.Dispatching import edu.stanford.spezi.core.logging.speziLogger import edu.stanford.spezi.core.utils.MessageNotifier @@ -31,21 +31,25 @@ class HealthSummaryService @Inject constructor( "${context.packageName}.provider", savePdfToFile ) - Intent(Intent.ACTION_VIEW).run { + val pdfIntent = Intent(Intent.ACTION_VIEW).apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) setDataAndType(pdfUri, MIME_TYPE_PDF) - context.startActivity(this) + } + if (pdfIntent.resolveActivity(context.packageManager) == null) { + messageNotifier.notify(messageId = R.string.no_pdf_reader_app_installed_error_message) + } else { + context.startActivity(pdfIntent) } }.onFailure { - messageNotifier.notify("Failed to generate Health Summary") + messageNotifier.notify(messageId = R.string.health_summary_generate_error_message) } } private fun savePdfToFile(pdfBytes: ByteArray): File { logger.i { "PDF size: ${pdfBytes.size}" } - val storageDir = - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + + val storageDir = context.getExternalFilesDir(null) ?: context.filesDir if (!storageDir.exists()) { storageDir.mkdirs() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 46fc54299..0399e4cf4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -122,4 +122,6 @@ Dizziness Score Your dizziness is important to keep track of because dizziness can be a side effect of your heart or your heart medicines. Error while handling message action. + Health Summary generated successfully, but no PDF reader is installed on the device + Failed to generate Health Summary diff --git a/core/utils/build.gradle.kts b/core/utils/build.gradle.kts index ea20b01da..7139dbe61 100644 --- a/core/utils/build.gradle.kts +++ b/core/utils/build.gradle.kts @@ -7,6 +7,10 @@ plugins { android { namespace = "edu.stanford.spezi.utils" + + defaultConfig { + testInstrumentationRunner = "edu.stanford.spezi.core.utils.HiltApplicationTestRunner" + } } dependencies { @@ -16,4 +20,5 @@ dependencies { implementation(libs.kotlinx.serialization.json) testImplementation(libs.bundles.unit.testing) + androidTestImplementation(libs.bundles.unit.testing) } diff --git a/core/utils/src/androidTest/kotlin/edu/stanford/spezi/core/utils/HiltApplicationTestRunner.kt b/core/utils/src/androidTest/kotlin/edu/stanford/spezi/core/utils/HiltApplicationTestRunner.kt new file mode 100644 index 000000000..2464d6baa --- /dev/null +++ b/core/utils/src/androidTest/kotlin/edu/stanford/spezi/core/utils/HiltApplicationTestRunner.kt @@ -0,0 +1,13 @@ +package edu.stanford.spezi.core.utils + +import android.app.Application +import android.content.Context +import androidx.test.runner.AndroidJUnitRunner +import dagger.hilt.android.testing.HiltTestApplication + +@Suppress("Unused") +class HiltApplicationTestRunner : AndroidJUnitRunner() { + override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application { + return super.newApplication(cl, HiltTestApplication::class.java.name, context) + } +} diff --git a/core/utils/src/androidTest/kotlin/edu/stanford/spezi/core/utils/MessageNotifierTest.kt b/core/utils/src/androidTest/kotlin/edu/stanford/spezi/core/utils/MessageNotifierTest.kt new file mode 100644 index 000000000..fffaf4274 --- /dev/null +++ b/core/utils/src/androidTest/kotlin/edu/stanford/spezi/core/utils/MessageNotifierTest.kt @@ -0,0 +1,38 @@ +package edu.stanford.spezi.core.utils + +import androidx.test.platform.app.InstrumentationRegistry +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import javax.inject.Inject + +@HiltAndroidTest +class MessageNotifierTest { + + @get:Rule(order = 1) + val hiltRule = HiltAndroidRule(this) + + @Inject + lateinit var messageNotifier: MessageNotifier + + @Before + fun init() { + hiltRule.inject() + } + + @Test + fun `it should not fail when toasting in main thread`() { + InstrumentationRegistry.getInstrumentation().runOnMainSync { + messageNotifier.notify("Some message") + } + } + + @Test + fun `it should not fail when notifying on a background thread`() = runTest(StandardTestDispatcher()) { + messageNotifier.notify("Some message") + } +} diff --git a/core/utils/src/main/kotlin/edu/stanford/spezi/core/utils/MessageNotifier.kt b/core/utils/src/main/kotlin/edu/stanford/spezi/core/utils/MessageNotifier.kt index 3b83ba4c5..ad7c1da00 100644 --- a/core/utils/src/main/kotlin/edu/stanford/spezi/core/utils/MessageNotifier.kt +++ b/core/utils/src/main/kotlin/edu/stanford/spezi/core/utils/MessageNotifier.kt @@ -1,6 +1,8 @@ package edu.stanford.spezi.core.utils import android.content.Context +import android.os.Handler +import android.os.Looper import android.widget.Toast import androidx.annotation.StringRes import dagger.hilt.android.qualifiers.ApplicationContext @@ -9,9 +11,15 @@ import javax.inject.Inject class MessageNotifier @Inject constructor( @ApplicationContext private val context: Context, ) { + private val mainHandler by lazy { Handler(Looper.getMainLooper()) } fun notify(message: String, duration: Duration = Duration.SHORT) { - Toast.makeText(context, message, duration.value).show() + val toast = { Toast.makeText(context, message, duration.value).show() } + if (Looper.myLooper() == Looper.getMainLooper()) { + toast() + } else { + mainHandler.post { toast() } + } } fun notify(@StringRes messageId: Int, duration: Duration = Duration.SHORT) {