diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/miscinfo/GoogleServicesState.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/miscinfo/GoogleServicesState.kt new file mode 100644 index 0000000000000000000000000000000000000000..5121eb59b6dbaf664a8bb6113ef77de2f6deb932 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/miscinfo/GoogleServicesState.kt @@ -0,0 +1,6 @@ +package de.rki.coronawarnapp.miscinfo + +data class GoogleServicesState( + val gmsVersion: Long?, + val enfVersion: Long? +) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/miscinfo/MiscInfoFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/miscinfo/MiscInfoFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..599dbd36c5fa9e20e5b4b5623106202369c64e79 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/miscinfo/MiscInfoFragment.kt @@ -0,0 +1,54 @@ +package de.rki.coronawarnapp.miscinfo + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import com.google.android.material.snackbar.Snackbar +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.FragmentTestDeviceinfoBinding +import de.rki.coronawarnapp.test.menu.ui.TestMenuItem +import de.rki.coronawarnapp.util.di.AutoInject +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.cwaViewModels +import javax.inject.Inject + +@SuppressLint("SetTextI18n") +class MiscInfoFragment : Fragment(R.layout.fragment_test_deviceinfo), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val vm: MiscInfoFragmentViewModel by cwaViewModels { viewModelFactory } + + private val binding: FragmentTestDeviceinfoBinding by viewBindingLazy() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + vm.errorEvent.observe2(this) { + Snackbar.make(requireView(), it, Snackbar.LENGTH_INDEFINITE).show() + } + + vm.versionState.observe2(this) { + binding.googlePlayServicesVersionInfo.text = "Google Play Services: ${it.gmsVersion}" + binding.exposureNotificationServiceVersionInfo.text = "Exposure Notification Services: ${it.enfVersion}" + } + + vm.inActiveTracingIntervals.observe2(this) { + binding.tracingInfosInactiveIntervals.text = "Inactive tracing intervals:\n$it" + } + + vm.tracingDaysInRetention.observe2(this) { + binding.tracingInfosActiveTracingRetention.text = "Active tracing days in retention period: $it" + } + } + + companion object { + val MENU_ITEM = TestMenuItem( + title = "Device Infos options", + description = "GMS/ENF Versions", + targetId = R.id.miscInfoFragment + ) + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/miscinfo/MiscInfoFragmentModule.kt similarity index 51% rename from Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentModule.kt rename to Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/miscinfo/MiscInfoFragmentModule.kt index 38504b80936698889c42139f31388e5d4cf1f2e9..bce9c986c71917c2ef3309ee09d01e203758efa2 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentModule.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/miscinfo/MiscInfoFragmentModule.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.test.api.ui +package de.rki.coronawarnapp.miscinfo import dagger.Binds import dagger.Module @@ -8,9 +8,11 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey @Module -abstract class TestForApiFragmentModule { +abstract class MiscInfoFragmentModule { @Binds @IntoMap - @CWAViewModelKey(TestForApiFragmentViewModel::class) - abstract fun testRiskLevelFragment(factory: TestForApiFragmentViewModel.Factory): CWAViewModelFactory<out CWAViewModel> + @CWAViewModelKey(MiscInfoFragmentViewModel::class) + abstract fun testTaskControllerFragment( + factory: MiscInfoFragmentViewModel.Factory + ): CWAViewModelFactory<out CWAViewModel> } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/miscinfo/MiscInfoFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/miscinfo/MiscInfoFragmentViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..13ea95fa031a605e9a58ce1db8e68a994388d683 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/miscinfo/MiscInfoFragmentViewModel.kt @@ -0,0 +1,64 @@ +package de.rki.coronawarnapp.miscinfo + +import android.content.Context +import androidx.core.content.pm.PackageInfoCompat +import androidx.lifecycle.asLiveData +import com.google.android.gms.common.GoogleApiAvailability +import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.nearby.ENFClient +import de.rki.coronawarnapp.risk.TimeVariables +import de.rki.coronawarnapp.storage.tracing.TracingIntervalRepository +import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.di.AppContext +import de.rki.coronawarnapp.util.ui.SingleLiveEvent +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory +import kotlinx.coroutines.flow.flow +import org.joda.time.Instant +import timber.log.Timber + +class MiscInfoFragmentViewModel @AssistedInject constructor( + @AppContext private val context: Context, + private val enfClient: ENFClient, + dispatcherProvider: DispatcherProvider +) : CWAViewModel(dispatcherProvider = dispatcherProvider) { + + val errorEvent = SingleLiveEvent<String>() + + val versionState = flow { + val enfVersion = enfClient.getENFClientVersion() + + val gmsVersion = try { + PackageInfoCompat.getLongVersionCode( + context.packageManager.getPackageInfo( + GoogleApiAvailability.GOOGLE_PLAY_SERVICES_PACKAGE, + 0 + ) + ) + } catch (e: Exception) { + Timber.e(e, "Failed to get GMS PackageInfo") + errorEvent.postValue(e.toString()) + null + } + + emit( + GoogleServicesState(gmsVersion = gmsVersion, enfVersion = enfVersion).also { + Timber.i("Google Service Infos: %s", it) + } + ) + }.asLiveData(context = dispatcherProvider.Default) + + val inActiveTracingIntervals = flow { + TracingIntervalRepository.getDateRepository(context) + .getIntervals() + .map { Instant.ofEpochMilli(it.first) to Instant.ofEpochMilli(it.second) } + .let { emit(it.joinToString("\n")) } + }.asLiveData(context = dispatcherProvider.Default) + + val tracingDaysInRetention = flow { + emit(TimeVariables.getActiveTracingDaysInRetentionPeriod()) + }.asLiveData(context = dispatcherProvider.Default) + + @AssistedInject.Factory + interface Factory : SimpleCWAViewModelFactory<MiscInfoFragmentViewModel> +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/GoogleServicesState.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/GoogleServicesState.kt deleted file mode 100644 index c6052212966e2cefdfc21a1e17751c79263c755e..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/GoogleServicesState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package de.rki.coronawarnapp.test.api.ui - -data class GoogleServicesState( - val version: Long -) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt deleted file mode 100644 index c8063ec9aa534c0ec732621561cbfdbb5ab68779..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt +++ /dev/null @@ -1,397 +0,0 @@ -package de.rki.coronawarnapp.test.api.ui - -import android.annotation.SuppressLint -import android.content.Intent -import android.graphics.Bitmap -import android.graphics.Color -import android.os.Bundle -import android.util.Base64 -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.Toast -import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.RecyclerView -import androidx.viewpager2.widget.ViewPager2 -import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient -import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import com.google.protobuf.ByteString -import com.google.zxing.BarcodeFormat -import com.google.zxing.integration.android.IntentIntegrator -import com.google.zxing.integration.android.IntentResult -import com.google.zxing.qrcode.QRCodeWriter -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.appconfig.AppConfigProvider -import de.rki.coronawarnapp.databinding.FragmentTestForAPIBinding -import de.rki.coronawarnapp.exception.ExceptionCategory -import de.rki.coronawarnapp.exception.ExceptionCategory.INTERNAL -import de.rki.coronawarnapp.exception.TransactionException -import de.rki.coronawarnapp.exception.reporting.report -import de.rki.coronawarnapp.nearby.ENFClient -import de.rki.coronawarnapp.receiver.ExposureStateUpdateReceiver -import de.rki.coronawarnapp.risk.TimeVariables -import de.rki.coronawarnapp.risk.storage.RiskLevelStorage -import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange -import de.rki.coronawarnapp.sharing.ExposureSharingService -import de.rki.coronawarnapp.storage.AppDatabase -import de.rki.coronawarnapp.storage.tracing.TracingIntervalRepository -import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater -import de.rki.coronawarnapp.test.menu.ui.TestMenuItem -import de.rki.coronawarnapp.util.KeyFileHelper -import de.rki.coronawarnapp.util.di.AutoInject -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.cwaViewModels -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.joda.time.DateTime -import org.joda.time.DateTimeZone -import timber.log.Timber -import java.io.File -import java.lang.reflect.Type -import java.util.UUID -import javax.inject.Inject - -@SuppressWarnings("TooManyFunctions", "LongMethod") -class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), AutoInject { - - @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory - @Inject lateinit var enfClient: ENFClient - @Inject lateinit var tekHistoryUpdater: TEKHistoryUpdater - - // TODO: This is ugly, remove when refactoring the fragment - @Inject lateinit var appConfigProvider: AppConfigProvider - @Inject lateinit var riskLevelStorage: RiskLevelStorage - private val vm: TestForApiFragmentViewModel by cwaViewModels { viewModelFactory } - - companion object { - val MENU_ITEM = TestMenuItem( - title = "Test for API", - description = "A mix of API related test options.", - targetId = R.id.test_for_api_fragment - ) - - fun keysToJson(keys: List<TemporaryExposureKey>): String { - return Gson().toJson(keys).toString() - } - - fun jsonToKeys(json: String): Array<TemporaryExposureKey> { - val listType: Type = object : TypeToken<Array<TemporaryExposureKey?>?>() {}.type - return Gson().fromJson(json, listType) - } - } - - private var myExposureKeysJSON: String? = null - private var myExposureKeys: List<TemporaryExposureKey>? = mutableListOf() - private var otherExposureKey: AppleLegacyKeyExchange.Key? = null - private var otherExposureKeyList = mutableListOf<AppleLegacyKeyExchange.Key>() - - private lateinit var qrPager: ViewPager2 - private lateinit var qrPagerAdapter: RecyclerView.Adapter<QRPagerAdapter.QRViewHolder> - - // Data and View binding - private val binding: FragmentTestForAPIBinding by viewBindingLazy() - - @SuppressLint("SetTextI18n") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - qrPager = binding.qrCodeViewpager - qrPagerAdapter = QRPagerAdapter() - qrPager.adapter = qrPagerAdapter - - // GMS Info card - vm.gmsState.observe2(this) { state -> - binding.googlePlayServicesVersionInfo.text = - "Google Play Services version: ${state.version}" - } - - vm.infoEvent.observe2(this) { showToast(it) } - vm.errorEvents.observe2(this) { showToast(it.toString()) } - vm.permissionRequiredEvent.observe2(this) { permissionRequest -> - permissionRequest.invoke(requireActivity()) - } - - // Test action card - binding.apply { - buttonApiTestStart.setOnClickListener { vm.requestTracingPermission() } - buttonApiGetExposureKeys.setOnClickListener { getExposureKeys() } - - buttonApiScanQrCode.setOnClickListener { - IntentIntegrator.forSupportFragment(this@TestForAPIFragment) - .setOrientationLocked(false) - .setBeepEnabled(false) - .initiateScan() - } - - buttonApiShareMyKeys.setOnClickListener { shareMyKeys() } - buttonApiEnterOtherKeys.setOnClickListener { enterOtherKeys() } - - buttonApiSubmitKeys.setOnClickListener { - vm.launch { - try { - tekHistoryUpdater.callback = object : TEKHistoryUpdater.Callback { - override fun onTEKAvailable(teks: List<TemporaryExposureKey>) { - launch(context = Dispatchers.Main) { - myExposureKeysJSON = keysToJson(teks) - myExposureKeys = teks - qrPagerAdapter.notifyDataSetChanged() - } - } - - override fun onPermissionDeclined() { - launch(context = Dispatchers.Main) { - showToast("Permission declined") - } - } - - override fun onError(error: Throwable) { - launch(context = Dispatchers.Main) { - showToast(error.toString()) - } - } - } - - updateKeysDisplay() - - // SubmitDiagnosisKeysTransaction.start("123") - withContext(Dispatchers.Main) { - showToast("Key submission successful") - } - } catch (e: TransactionException) { - e.report(INTERNAL) - } - } - } - - buttonCalculateRiskLevel.setOnClickListener { vm.calculateRiskLevelClicked() } - - buttonInsertExposureSummary.setOnClickListener { - // Now broadcasts them to the worker. - val intent = Intent( - context, - ExposureStateUpdateReceiver::class.java - ) - intent.action = ExposureNotificationClient.ACTION_EXPOSURE_STATE_UPDATED - context?.sendBroadcast(intent) - } - - buttonRetrieveExposureSummary.setOnClickListener { - vm.launch { - val summary = riskLevelStorage.riskLevelResults.first().maxByOrNull { - it.calculatedAt - }?.toString() ?: "No results yet." - - withContext(Dispatchers.Main) { - showToast(summary) - } - } - } - - buttonClearDb.setOnClickListener { - vm.launch { - AppDatabase.getInstance(requireContext()).clearAllTables() - } - } - - buttonTracingIntervals.setOnClickListener { - vm.launch { - val intervals = TracingIntervalRepository.getDateRepository(requireContext()) - .getIntervals() - .toString() - withContext(Dispatchers.Main) { - showToast(intervals) - } - } - } - - buttonTracingDurationInRetentionPeriod.setOnClickListener { - vm.launch { - val daysInRetention = - TimeVariables.getActiveTracingDaysInRetentionPeriod().toString() - withContext(Dispatchers.Main) { - showToast(daysInRetention) - } - } - } - } - } - - private val prettyKey = { key: AppleLegacyKeyExchange.Key -> - StringBuilder() - .append("\nKey data: ${key.keyData}") - .append("\nRolling start number: ${key.rollingStartNumber}") - .append("\nRisk period: ${key.rollingPeriod}") - .toString() - } - - private val onScannedKey = { key: AppleLegacyKeyExchange.Key? -> - Timber.i("keys scanned..") - key?.let { - binding.textScannedKey.text = prettyKey(key) - binding.textScannedKey.visibility = View.VISIBLE - } - otherExposureKeyList.add(key!!) - otherExposureKey = key - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - vm.handleActivityResult(requestCode, resultCode, data) - - val result: IntentResult? = IntentIntegrator.parseActivityResult(requestCode, resultCode, data) - if (result != null) { - if (result.contents == null) { - showToast("Cancelled") - } else { - ExposureSharingService.getOthersKeys(result.contents, onScannedKey) - } - } else { - super.onActivityResult(requestCode, resultCode, data) - } - } - - private fun getExposureKeys() { - tekHistoryUpdater.updateTEKHistoryOrRequestPermission { permissionRequest -> - permissionRequest(requireActivity()) - } - } - - private fun shareMyKeys() { - if (myExposureKeysJSON != null) { - val share = Intent.createChooser(Intent().apply { - action = Intent.ACTION_SEND - type = "text/plain" - putExtra(Intent.EXTRA_TEXT, myExposureKeysJSON) - }, null) - startActivity(share) - } else { - showToast("No Exposure Keys to share - Press 'Get Exposure Keys' first") - } - } - - private fun enterOtherKeys() { - if (null == otherExposureKey) { - showToast("No other keys provided. Please fill the EditText with the JSON containing keys") - } else { - val appleKeyList = mutableListOf<AppleLegacyKeyExchange.Key>() - - for (key in otherExposureKeyList) { - appleKeyList.add( - AppleLegacyKeyExchange.Key.newBuilder() - .setKeyData(key.keyData) - .setRollingPeriod(144) - .setRollingStartNumber(key.rollingStartNumber) - .setTransmissionRiskLevel(1) - .build() - ) - } - - val appleFiles = listOf( - AppleLegacyKeyExchange.File.newBuilder() - .addAllKeys(appleKeyList) - .build() - ) - - val dir = File( - File(requireContext().getExternalFilesDir(null), "key-export"), - UUID.randomUUID().toString() - ) - dir.mkdirs() - - var googleFileList: List<File> - lifecycleScope.launch { - googleFileList = KeyFileHelper.asyncCreateExportFiles(appleFiles, dir) - - Timber.i("Provide ${googleFileList.count()} files with ${appleKeyList.size} keys") - try { - // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API - enfClient.provideDiagnosisKeys( - googleFileList, - appConfigProvider.getAppConfig().diagnosisKeysDataMapping - ) - showToast("Provided ${appleKeyList.size} keys to Google API") - } catch (e: Exception) { - e.report(ExceptionCategory.EXPOSURENOTIFICATION) - } - } - } - } - - private fun updateKeysDisplay() { - - val myKeys = - if (myExposureKeysJSON != null) jsonToKeys( - myExposureKeysJSON!! - ) else null - - val myKeysLabelAndCount = getString( - R.string.test_api_body_my_keys, - myKeys?.size ?: 0 - ) - binding.labelMyKeys.text = myKeysLabelAndCount - binding.textMyKeys.text = myExposureKeysJSON - - myKeys - ?.maxByOrNull { it.rollingStartIntervalNumber } - ?.rollingStartIntervalNumber?.toLong() - ?.let { - val ms = it * 60L * 10L * 1000L - val dateString = DateTime(ms, DateTimeZone.UTC) - - binding.labelLatestKeyDate.text = "Latest key is from: $dateString" - } - } - - private fun showToast(message: String) { - Toast.makeText(context, message, Toast.LENGTH_LONG).show() - } - - private inner class QRPagerAdapter : - RecyclerView.Adapter<QRPagerAdapter.QRViewHolder>() { - - inner class QRViewHolder(val qrCode: ImageView) : RecyclerView.ViewHolder(qrCode) - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QRViewHolder { - val imageView = LayoutInflater.from(parent.context) - .inflate(R.layout.test_qr_code_view, parent, false) as ImageView - return QRViewHolder(imageView) - } - - override fun getItemCount(): Int = myExposureKeys?.size ?: 0 - - override fun onBindViewHolder(holder: QRViewHolder, position: Int) { - myExposureKeys?.get(position)?.let { - holder.qrCode.setImageBitmap(bitmapForImage(it)) - } - } - - private fun bitmapForImage(key: TemporaryExposureKey): Bitmap { - val legacyKey = AppleLegacyKeyExchange.Key.newBuilder() - .setKeyData(ByteString.copyFrom(key.keyData)) - .setRollingPeriod(key.rollingPeriod) - .setRollingStartNumber(key.rollingStartIntervalNumber) - .build().toByteArray() - val bMatrix = QRCodeWriter().encode( - Base64.encodeToString(legacyKey, Base64.DEFAULT), - BarcodeFormat.QR_CODE, - 300, - 300 - ) - val bmp = - Bitmap.createBitmap(bMatrix.width, bMatrix.height, Bitmap.Config.RGB_565) - for (x in 0 until bMatrix.width) { - for (y in 0 until bMatrix.height) { - bmp.setPixel(x, y, if (bMatrix.get(x, y)) Color.BLACK else Color.WHITE) - } - } - return bmp - } - } -} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt deleted file mode 100644 index aeb8db09625087ec38725dd40518725fbedde119..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForApiFragmentViewModel.kt +++ /dev/null @@ -1,70 +0,0 @@ -package de.rki.coronawarnapp.test.api.ui - -import android.app.Activity -import android.content.Context -import android.content.Intent -import androidx.core.content.pm.PackageInfoCompat -import com.google.android.gms.common.GoogleApiAvailability -import com.squareup.inject.assisted.AssistedInject -import de.rki.coronawarnapp.nearby.TracingPermissionHelper -import de.rki.coronawarnapp.risk.RiskLevelTask -import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater -import de.rki.coronawarnapp.task.TaskController -import de.rki.coronawarnapp.task.common.DefaultTaskRequest -import de.rki.coronawarnapp.util.di.AppContext -import de.rki.coronawarnapp.util.ui.SingleLiveEvent -import de.rki.coronawarnapp.util.ui.smartLiveData -import de.rki.coronawarnapp.util.viewmodel.CWAViewModel -import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory - -class TestForApiFragmentViewModel @AssistedInject constructor( - @AppContext private val context: Context, - private val taskController: TaskController, - private val tracingPermissionHelper: TracingPermissionHelper, - private val tekHistoryUpdater: TEKHistoryUpdater -) : CWAViewModel() { - - val errorEvents = SingleLiveEvent<Throwable>() - val infoEvent = SingleLiveEvent<String>() - val permissionRequiredEvent = SingleLiveEvent<(Activity) -> Unit>() - - init { - tracingPermissionHelper.callback = object : TracingPermissionHelper.Callback { - override fun onUpdateTracingStatus(isTracingEnabled: Boolean) { - infoEvent.postValue("isTracingEnabled: $isTracingEnabled") - } - - override fun onError(error: Throwable) { - errorEvents.postValue(error) - } - } - } - - fun calculateRiskLevelClicked() { - taskController.submit(DefaultTaskRequest(RiskLevelTask::class, originTag = "TestForApiFragmentViewModel")) - } - - fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - tracingPermissionHelper.handleActivityResult(requestCode, resultCode, data) - } - - fun requestTracingPermission() { - tracingPermissionHelper.startTracing { permissionRequest -> - permissionRequiredEvent.postValue(permissionRequest) - } - } - - val gmsState by smartLiveData { - GoogleServicesState( - version = PackageInfoCompat.getLongVersionCode( - context.packageManager.getPackageInfo( - GoogleApiAvailability.GOOGLE_PLAY_SERVICES_PACKAGE, - 0 - ) - ) - ) - } - - @AssistedInject.Factory - interface Factory : SimpleCWAViewModelFactory<TestForApiFragmentViewModel> -} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt index ef3eff6e88c7362e04b9766d57312bedf0c536ae..86aac8783ba37f942b002fc494892ae2efb0e30e 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.test.menu.ui import androidx.lifecycle.MutableLiveData import com.squareup.inject.assisted.AssistedInject -import de.rki.coronawarnapp.test.api.ui.TestForAPIFragment +import de.rki.coronawarnapp.miscinfo.MiscInfoFragment import de.rki.coronawarnapp.test.appconfig.ui.AppConfigTestFragment import de.rki.coronawarnapp.test.crash.ui.SettingsCrashReportFragment import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragment @@ -20,12 +20,12 @@ class TestMenuFragmentViewModel @AssistedInject constructor() : CWAViewModel() { listOf( DebugOptionsFragment.MENU_ITEM, AppConfigTestFragment.MENU_ITEM, - TestForAPIFragment.MENU_ITEM, TestRiskLevelCalculationFragment.MENU_ITEM, KeyDownloadTestFragment.MENU_ITEM, TestTaskControllerFragment.MENU_ITEM, SubmissionTestFragment.MENU_ITEM, - SettingsCrashReportFragment.MENU_ITEM + SettingsCrashReportFragment.MENU_ITEM, + MiscInfoFragment.MENU_ITEM ).let { MutableLiveData(it) } } val showTestScreenEvent = SingleLiveEvent<TestMenuItem>() diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt index 91fc831726f3b1c4532c61472fdede63e36a1b71..030989be8146dc9ba1e20e44ed22b75e93ee65d9 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/submission/ui/SubmissionTestFragmentViewModel.kt @@ -39,19 +39,21 @@ class SubmissionTestFragmentViewModel @AssistedInject constructor( val shareTEKsEvent = SingleLiveEvent<TEKExport>() - val tekHistory: LiveData<List<TEKHistoryItem>> = tekHistoryStorage.tekData.map { items -> - items.flatMap { batch -> - batch.keys - .map { key -> - TEKHistoryItem( - obtainedAt = batch.obtainedAt, - batchId = batch.batchId, - key = key - ) - } - .sortedBy { it.obtainedAt } + val tekHistory: LiveData<List<TEKHistoryItem>> = tekHistoryStorage.tekData + .map { items -> + items.flatMap { batch -> + batch.keys + .map { key -> + TEKHistoryItem( + obtainedAt = batch.obtainedAt, + batchId = batch.batchId, + key = key + ) + } + } } - }.asLiveData(context = dispatcherProvider.Default) + .map { historyItems -> historyItems.sortedBy { it.obtainedAt } } + .asLiveData(context = dispatcherProvider.Default) init { tekHistoryUpdater.callback = object : TEKHistoryUpdater.Callback { diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt index e406155bac41e9b12c5287b985790baad7617a8f..e368eab9f32d175fa0b6c15d29c3143eb52255a4 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt @@ -2,8 +2,8 @@ package de.rki.coronawarnapp.ui.main import dagger.Module import dagger.android.ContributesAndroidInjector -import de.rki.coronawarnapp.test.api.ui.TestForAPIFragment -import de.rki.coronawarnapp.test.api.ui.TestForApiFragmentModule +import de.rki.coronawarnapp.miscinfo.MiscInfoFragment +import de.rki.coronawarnapp.miscinfo.MiscInfoFragmentModule import de.rki.coronawarnapp.test.appconfig.ui.AppConfigTestFragment import de.rki.coronawarnapp.test.appconfig.ui.AppConfigTestFragmentModule import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragment @@ -28,8 +28,8 @@ abstract class MainActivityTestModule { @ContributesAndroidInjector(modules = [TestRiskLevelCalculationFragmentModule::class]) abstract fun testRiskLevelCalculationFragment(): TestRiskLevelCalculationFragment - @ContributesAndroidInjector(modules = [TestForApiFragmentModule::class]) - abstract fun testRiskLevelApiFragment(): TestForAPIFragment + @ContributesAndroidInjector(modules = [MiscInfoFragmentModule::class]) + abstract fun miscInfoFragment(): MiscInfoFragment @ContributesAndroidInjector(modules = [TestTaskControllerFragmentModule::class]) abstract fun testTaskControllerFragment(): TestTaskControllerFragment diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_deviceinfo.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_deviceinfo.xml new file mode 100644 index 0000000000000000000000000000000000000000..f7d6edb921beddf7576b23566c92329b50ee9472 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_deviceinfo.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + tools:ignore="HardcodedText"> + + <androidx.core.widget.NestedScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_tiny" + android:orientation="vertical"> + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/gms_container" + style="@style/card" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_tiny"> + + <TextView + android:id="@+id/gms_container_title" + style="@style/headline6" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="API Infos" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/google_play_services_version_info" + style="@style/body2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:text="Google Play Services: ?" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/gms_container_title" /> + + <TextView + android:id="@+id/exposure_notification_service_version_info" + style="@style/body2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:text="Exposure Notification Services: ?" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/google_play_services_version_info" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/tracing_container" + style="@style/card" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_tiny"> + + <TextView + android:id="@+id/tracing_container_title" + style="@style/headline6" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Tracing Infos" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/tracing_infos_inactive_intervals" + style="@style/body2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:text="Inactive tracing intervals: ?" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tracing_container_title" /> + + <TextView + android:id="@+id/tracing_infos_active_tracing_retention" + style="@style/body2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:text="Active tracing days in retention period: ?" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tracing_infos_inactive_intervals" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </LinearLayout> + </androidx.core.widget.NestedScrollView> +</layout> diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml deleted file mode 100644 index 30b10c80b5acd13b4e1e7963521ba2fe44500099..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_for_a_p_i.xml +++ /dev/null @@ -1,225 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<layout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - tools:ignore="HardcodedText"> - - <androidx.core.widget.NestedScrollView - android:layout_width="match_parent" - android:layout_height="match_parent" - android:fillViewport="true"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_margin="@dimen/spacing_tiny" - android:orientation="vertical"> - - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/gms_container" - style="@style/card" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_margin="@dimen/spacing_tiny"> - - <TextView - android:id="@+id/gms_container_title" - style="@style/headline6" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="GMS Infos" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <TextView - android:id="@+id/google_play_services_version_info" - style="@style/body2" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:text="Google Play Services Version: ?" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/gms_container_title" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - - <LinearLayout - android:id="@+id/exposure_summary_container" - style="@style/card" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_margin="@dimen/spacing_tiny" - android:orientation="vertical"> - - <TextView - android:id="@+id/label_exposure_summary" - style="@style/headline6" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/spacing_tiny" - android:text="@string/test_api_exposure_summary_headline" /> - - <Button - android:id="@+id/button_api_scan_qr_code" - style="@style/buttonPrimary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_small" - android:text="@string/test_api_button_scan_qr_code" /> - - <Button - android:id="@+id/button_api_enter_other_keys" - style="@style/buttonPrimary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:text="@string/test_api_button_enter_other_keys" /> - </LinearLayout> - - <LinearLayout - android:id="@+id/mykeys_container" - style="@style/card" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_margin="@dimen/spacing_tiny" - android:orientation="vertical"> - - <TextView - android:id="@+id/label_my_keys" - style="@style/headline6" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/spacing_tiny" - android:text="@string/test_api_body_my_keys" /> - - <TextView - android:id="@+id/label_latest_key_date" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="Latest key is from: -" /> - - <TextView - android:id="@+id/text_my_keys" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="Your keys will be displayed here" - android:lines="5" - android:visibility="gone" /> - - <androidx.viewpager2.widget.ViewPager2 - android:id="@+id/qr_code_viewpager" - android:layout_width="match_parent" - android:layout_height="200dp" /> - - <TextView - android:id="@+id/label_other_keys" - style="@style/headline6" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/test_api_body_other_keys" /> - - <TextView - android:id="@+id/text_scanned_key" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - - </LinearLayout> - - <LinearLayout - android:id="@+id/testactions_container" - style="@style/card" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_margin="@dimen/spacing_tiny" - android:orientation="vertical"> - - <Button - android:id="@+id/button_api_test_start" - style="@style/buttonPrimary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/test_api_button_start" /> - - <Button - android:id="@+id/button_api_get_exposure_keys" - style="@style/buttonPrimary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:text="@string/test_api_button_get_exposure_keys" /> - - <Button - android:id="@+id/button_api_submit_keys" - style="@style/buttonPrimary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:text="@string/test_api_button_submit_keys" /> - - <Button - android:id="@+id/button_api_share_my_keys" - style="@style/buttonPrimary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:text="@string/test_api_button_share_my_keys" /> - - <Button - android:id="@+id/button_calculate_risk_level" - style="@style/buttonPrimary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:text="@string/test_api_calculate_risk_level" /> - - <Button - android:id="@+id/button_insert_exposure_summary" - style="@style/buttonPrimary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:text="Insert ExposureSummary" /> - - <Button - android:id="@+id/button_retrieve_exposure_summary" - style="@style/buttonPrimary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:text="Retrieve ExposureSummary" /> - - <Button - android:id="@+id/button_clear_db" - style="@style/buttonPrimary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:text="Clear Internal DB" /> - - <Button - android:id="@+id/button_tracing_intervals" - style="@style/buttonPrimary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:text="Get Inactive Tracing Intervals" /> - - <Button - android:id="@+id/button_tracing_duration_in_retention_period" - style="@style/buttonPrimary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:text="Get Active Tracing Duration in Retention Period" /> - </LinearLayout> - - <de.rki.coronawarnapp.ui.calendar.CalendarView - android:id="@+id/calendar_container" - android:layout_width="match_parent" - android:layout_height="match_parent" /> - - </LinearLayout> - </androidx.core.widget.NestedScrollView> -</layout> diff --git a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml index 14d89e5a3de75e9d667feafc4a67a991d2e0759d..d1919dec94661e5fbd9c0c907f427690f2705e1d 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml @@ -13,9 +13,6 @@ <action android:id="@+id/action_testMenuFragment_to_settingsCrashReportFragment" app:destination="@id/test_bug_report_fragment" /> - <action - android:id="@+id/action_testMenuFragment_to_testForAPIFragment" - app:destination="@id/test_for_api_fragment" /> <action android:id="@+id/action_testMenuFragment_to_testRiskLevelCalculation" app:destination="@id/test_risklevel_calculation_fragment" /> @@ -34,14 +31,11 @@ <action android:id="@+id/action_test_menu_fragment_to_submissionTestFragment" app:destination="@id/test_submission_fragment" /> + <action + android:id="@+id/action_test_menu_fragment_to_miscInfoFragment" + app:destination="@id/miscInfoFragment" /> </fragment> - <fragment - android:id="@+id/test_for_api_fragment" - android:name="de.rki.coronawarnapp.test.api.ui.TestForAPIFragment" - android:label="@layout/fragment_test_for_a_p_i" - tools:layout="@layout/fragment_test_for_a_p_i" /> - <fragment android:id="@+id/test_bug_report_fragment" android:name="de.rki.coronawarnapp.test.crash.ui.SettingsCrashReportFragment" @@ -94,5 +88,9 @@ android:name="de.rki.coronawarnapp.test.submission.ui.SubmissionTestFragment" android:label="SubmissionTestFragment" tools:layout="@layout/fragment_test_submission" /> + <fragment + android:id="@+id/miscInfoFragment" + android:name="de.rki.coronawarnapp.miscinfo.MiscInfoFragment" + android:label="MiscInfoFragment" /> </navigation>