From 8b4b7897ad16348a0fc9f30c510f51c9ec30bed4 Mon Sep 17 00:00:00 2001 From: BMItter <46747780+BMItter@users.noreply.github.com> Date: Wed, 25 Nov 2020 14:35:53 +0100 Subject: [PATCH] Exposurewindows adjustments & share, Testmenu (EXPOSUREAPP-3898) (#1710) * preparation share exposureWindows via email etc. - wip * Exposure Windows can be shared if data is availabe * Manifest and provider path deviceForTesters only * fixed strange conflicts * klint * Pretty exposure window export * removed testlog * removed relicts --- .../src/deviceForTesters/AndroidManifest.xml | 21 ++++++++ .../risklevel/entities/ExposureWindowJson.kt | 19 +++++++ .../risklevel/entities/ScanInstanceJson.kt | 15 ++++++ .../ui/TestRiskLevelCalculationFragment.kt | 51 ++++++++++++------- ...iskLevelCalculationFragmentCWAViewModel.kt | 42 ++++++++++++--- .../fragment_test_risk_level_calculation.xml | 19 ++++--- .../res/xml/provider_paths.xml | 4 ++ 7 files changed, 137 insertions(+), 34 deletions(-) create mode 100644 Corona-Warn-App/src/deviceForTesters/AndroidManifest.xml create mode 100644 Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/entities/ExposureWindowJson.kt create mode 100644 Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/entities/ScanInstanceJson.kt create mode 100644 Corona-Warn-App/src/deviceForTesters/res/xml/provider_paths.xml diff --git a/Corona-Warn-App/src/deviceForTesters/AndroidManifest.xml b/Corona-Warn-App/src/deviceForTesters/AndroidManifest.xml new file mode 100644 index 000000000..ca1b20da3 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:ignore="LockedOrientationActivity" + package="de.rki.coronawarnapp"> + + <application> + + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="${applicationId}.fileProvider" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/provider_paths"/> + </provider> + + </application> + +</manifest> \ No newline at end of file diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/entities/ExposureWindowJson.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/entities/ExposureWindowJson.kt new file mode 100644 index 000000000..ad35b6454 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/entities/ExposureWindowJson.kt @@ -0,0 +1,19 @@ +package de.rki.coronawarnapp.test.risklevel.entities + +import com.google.android.gms.nearby.exposurenotification.ExposureWindow + +data class ExposureWindowJson( + val dateMillisSinceEpoch: Long, + val reportType: Int, + val infectiousness: Int, + val calibrationConfidence: Int, + val scanInstances: List<ScanInstanceJson> +) + +fun ExposureWindow.toExposureWindowJson(): ExposureWindowJson = ExposureWindowJson( + dateMillisSinceEpoch = dateMillisSinceEpoch, + reportType = reportType, + infectiousness = infectiousness, + calibrationConfidence = calibrationConfidence, + scanInstances = scanInstances.map { it.toScanInstanceJson() } +) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/entities/ScanInstanceJson.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/entities/ScanInstanceJson.kt new file mode 100644 index 000000000..0b42a6bc7 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/entities/ScanInstanceJson.kt @@ -0,0 +1,15 @@ +package de.rki.coronawarnapp.test.risklevel.entities + +import com.google.android.gms.nearby.exposurenotification.ScanInstance + +data class ScanInstanceJson( + val typicalAttenuationDb: Int, + val minAttenuationDb: Int, + val secondsSinceLastScan: Int +) + +fun ScanInstance.toScanInstanceJson(): ScanInstanceJson = ScanInstanceJson( + typicalAttenuationDb = typicalAttenuationDb, + minAttenuationDb = minAttenuationDb, + secondsSinceLastScan = secondsSinceLastScan +) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt index 7087a044d..e3b014dea 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt @@ -1,10 +1,13 @@ package de.rki.coronawarnapp.test.risklevel.ui +import android.content.Intent import android.os.Bundle import android.view.View import android.widget.RadioButton import android.widget.RadioGroup import android.widget.Toast +import androidx.core.app.ShareCompat +import androidx.core.content.FileProvider import androidx.core.view.ViewCompat import androidx.core.view.children import androidx.fragment.app.Fragment @@ -20,6 +23,8 @@ import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted +import timber.log.Timber +import java.io.File import javax.inject.Inject @Suppress("LongMethod") @@ -42,49 +47,43 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - vm.tracingCardState.observe2(this) { binding.tracingCard = it } - binding.settingsViewModel = settingsViewModel - vm.showRiskStatusCard.observe2(this) { binding.showRiskStatusCard = it } - binding.buttonRetrieveDiagnosisKeys.setOnClickListener { vm.retrieveDiagnosisKeys() } binding.buttonCalculateRiskLevel.setOnClickListener { vm.calculateRiskLevel() } binding.buttonClearDiagnosisKeyCache.setOnClickListener { vm.clearKeyCache() } - binding.buttonResetRiskLevel.setOnClickListener { vm.resetRiskLevel() } + binding.buttonExposureWindowsShare.setOnClickListener { vm.shareExposureWindows() } vm.riskLevelResetEvent.observe2(this) { Toast.makeText( requireContext(), "Reset done, please fetch diagnosis keys from server again", Toast.LENGTH_SHORT ).show() } - vm.additionalRiskCalcInfo.observe2(this) { binding.labelRiskAdditionalInfo.text = it } - vm.aggregatedRiskResult.observe2(this) { binding.labelAggregatedRiskResult.text = it } - - vm.exposureWindowCountString.observe2(this) { - binding.labelExposureWindowCount.text = it - } - - vm.exposureWindows.observe2(this) { - binding.labelExposureWindows.text = it - } - vm.backendParameters.observe2(this) { binding.labelBackendParameters.text = it } - + vm.exposureWindowCount.observe2(this) { exposureWindowCount -> + binding.labelExposureWindowCount.text = "Retrieved $exposureWindowCount Exposure Windows" + binding.buttonExposureWindowsShare.visibility = when (exposureWindowCount > 0) { + true -> View.VISIBLE + false -> View.GONE + } + } + vm.shareFileEvent.observe2(this) { + shareExposureWindowsFile(it) + } vm.fakeWindowsState.observe2(this) { currentType -> binding.apply { if (fakeWindowsToggleGroup.childCount != TestSettings.FakeExposureWindowTypes.values().size) { @@ -101,7 +100,6 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le } } } - fakeWindowsToggleGroup.children.forEach { it as RadioButton it.isChecked = it.text == currentType.name @@ -117,8 +115,23 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le } } + private fun shareExposureWindowsFile(file: File) { + Timber.d("Opening Share-Intent for Exposure Windows") + val shareFileUri = + FileProvider.getUriForFile(requireContext(), requireContext().packageName + ".fileProvider", file) + val shareIntent = ShareCompat.IntentBuilder + .from(requireActivity()) + .setStream(shareFileUri) + .setType("text/plain") + .intent + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + if (shareIntent.resolveActivity(requireActivity().packageManager) != null) { + startActivity(shareIntent) + } + } + companion object { - val TAG: String = TestRiskLevelCalculationFragment::class.simpleName!! + private val TAG = TestRiskLevelCalculationFragment::class.java.simpleName val MENU_ITEM = TestMenuItem( title = "ENF v2 Calculation", description = "Window Mode related overview.", diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt index 8cbf5d39e..63afe0209 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt @@ -3,6 +3,8 @@ package de.rki.coronawarnapp.test.risklevel.ui import android.content.Context import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData +import com.google.gson.Gson +import com.google.gson.GsonBuilder import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.appconfig.AppConfigProvider @@ -34,11 +36,13 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.sample import kotlinx.coroutines.withContext import org.joda.time.Instant import timber.log.Timber +import java.io.File import java.util.Date import java.util.concurrent.TimeUnit @@ -46,7 +50,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( @Assisted private val handle: SavedStateHandle, @Assisted private val exampleArg: String?, @AppContext private val context: Context, // App context - dispatcherProvider: DispatcherProvider, + private val dispatcherProvider: DispatcherProvider, private val taskController: TaskController, private val keyCacheRepository: KeyCacheRepository, private val appConfigProvider: AppConfigProvider, @@ -57,6 +61,11 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( dispatcherProvider = dispatcherProvider ) { + // Use unique instance for pretty output + private val gson: Gson by lazy { + GsonBuilder().setPrettyPrinting().create() + } + val fakeWindowsState = testSettings.fakeExposureWindows.flow.asLiveData() init { @@ -66,6 +75,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( } val riskLevelResetEvent = SingleLiveEvent<Unit>() + val shareFileEvent = SingleLiveEvent<File>() val showRiskStatusCard = SubmissionRepository.deviceUIStateFlow.map { it.withSuccess(false) { true } @@ -75,14 +85,9 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( .sample(150L) .asLiveData(dispatcherProvider.Default) - val exposureWindowCountString = riskLevelStorage - .exposureWindows - .map { "Retrieved ${it.size} Exposure Windows" } - .asLiveData() - - val exposureWindows = riskLevelStorage + val exposureWindowCount = riskLevelStorage .exposureWindows - .map { if (it.isEmpty()) "Exposure windows list is empty" else it.toString() } + .map { it.size } .asLiveData() val aggregatedRiskResult = riskLevelStorage @@ -216,6 +221,27 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( } } + fun shareExposureWindows() { + Timber.d("Creating text file for Exposure Windows") + launch(dispatcherProvider.IO) { + val exposureWindows = riskLevelStorage.exposureWindows.firstOrNull() + + val path = File(context.cacheDir, "share/") + path.mkdirs() + + val file = File(path, "exposureWindows.txt") + file.bufferedWriter() + .use { + if (exposureWindows.isNullOrEmpty()) { + it.appendLine("Exposure windows list was empty") + } else { + it.appendLine(gson.toJson(exposureWindows)) + } + } + shareFileEvent.postValue(file) + } + } + fun clearKeyCache() { Timber.d("Clearing key cache") launch { keyCacheRepository.clear() } diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_risk_level_calculation.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_risk_level_calculation.xml index 0ea69b5c0..1e6d0bf1b 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_risk_level_calculation.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_risk_level_calculation.xml @@ -23,6 +23,7 @@ type="de.rki.coronawarnapp.ui.tracing.card.TracingCardState" /> </data> + <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" @@ -33,7 +34,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> - + <LinearLayout android:id="@+id/environment_container" style="@style/card" @@ -170,13 +171,17 @@ android:layout_height="wrap_content" android:text="-" /> - <TextView - android:id="@+id/label_exposure_windows" - android:layout_width="wrap_content" + <Button + android:id="@+id/buttonExposureWindowsShare" + style="@style/buttonPrimary" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="5dp" - android:text="-" /> + android:layout_marginTop="@dimen/spacing_normal" + android:layout_marginBottom="@dimen/spacing_normal" + android:text="Share ExposureWindows" /> </LinearLayout> </ScrollView> -</layout> \ No newline at end of file + + +</layout> diff --git a/Corona-Warn-App/src/deviceForTesters/res/xml/provider_paths.xml b/Corona-Warn-App/src/deviceForTesters/res/xml/provider_paths.xml new file mode 100644 index 000000000..b6522dfad --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/res/xml/provider_paths.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<paths xmlns:android="http://schemas.android.com/apk/res/android"> + <cache-path name="share" path="share/" /> +</paths> \ No newline at end of file -- GitLab