Skip to content
Snippets Groups Projects
Unverified Commit a5a6d244 authored by Thomas Klingbeil's avatar Thomas Klingbeil Committed by GitHub
Browse files

Enable diagnosis key upload to backend (#35)


* Moved camera permission check to dispatcher fragment

* Added illustrations for test result screen

* Updated result fragment to match new design

* Added constraints to status card

* Integrated submission result positive other warning fragment in ui flow

* add TAN fetching to the key submission transaction

* remove unneeded function

* Fix log message for fetching TAN

* Added submission done fragment to nav graph

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* Added ability to dialog helper to create dialog without negative button

* Switched from AlertDialog.Builder to DIalogHelper

* use actual TAN (authCode) for key submission

set the TAN in the corresponding header field
fixes #8

* request permission to retrieve diagnosis keys

* permission needs to be requested again after dialog has been presented to the users
* trigger transaction to retrieve TAN and upload keys

Co-authored-by: default avatarKolya Opahle <k.opahle@sap.com>
parent 88b43966
No related branches found
No related tags found
No related merge requests found
...@@ -203,7 +203,7 @@ object WebRequestBuilder { ...@@ -203,7 +203,7 @@ object WebRequestBuilder {
Response.Listener { response -> Response.Listener { response ->
Log.d( Log.d(
TAG, TAG,
"$requestID: Test Result Request successful" "$requestID: TAN Request successful"
) )
cont.resume(response) cont.resume(response)
}, },
......
...@@ -44,7 +44,7 @@ class KeySubmissionRequest( ...@@ -44,7 +44,7 @@ class KeySubmissionRequest(
val headers = HashMap<String, String>(super.getHeaders()) val headers = HashMap<String, String>(super.getHeaders())
if (faked) headers["cwa-fake"] = Math.random().toInt().toString() if (faked) headers["cwa-fake"] = Math.random().toInt().toString()
else headers["cwa-fake"] = "0" else headers["cwa-fake"] = "0"
headers["cwa-authorization"] = "TAN 123456" headers["cwa-authorization"] = authCode
this.addMarker("headers:$headers") this.addMarker("headers:$headers")
return headers return headers
} }
......
package de.rki.coronawarnapp.service.submission package de.rki.coronawarnapp.service.submission
import de.rki.coronawarnapp.exception.InvalidQRCodeExcpetion import de.rki.coronawarnapp.exception.InvalidQRCodeExcpetion
import de.rki.coronawarnapp.exception.NoAuthCodeSetException
import de.rki.coronawarnapp.exception.NoGUIDOrTANSetException import de.rki.coronawarnapp.exception.NoGUIDOrTANSetException
import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
import de.rki.coronawarnapp.http.WebRequestBuilder import de.rki.coronawarnapp.http.WebRequestBuilder
...@@ -49,23 +48,18 @@ object SubmissionService { ...@@ -49,23 +48,18 @@ object SubmissionService {
deleteTeleTAN() deleteTeleTAN()
} }
suspend fun asyncRequestAuthCode() { suspend fun asyncRequestAuthCode(): String {
val registrationToken = val registrationToken =
LocalData.registrationToken() ?: throw NoRegistrationTokenSetException() LocalData.registrationToken() ?: throw NoRegistrationTokenSetException()
val authCode = WebRequestBuilder.asyncGetTan(TAN_REQUEST_URL, registrationToken) val authCode = WebRequestBuilder.asyncGetTan(TAN_REQUEST_URL, registrationToken)
return authCode
LocalData.authCode(authCode)
deleteRegistrationToken()
} }
suspend fun asyncSubmitExposureKeys() { suspend fun asyncSubmitExposureKeys() {
val authCode = val registrationToken =
LocalData.authCode() ?: throw NoAuthCodeSetException() LocalData.registrationToken() ?: throw NoRegistrationTokenSetException()
SubmitDiagnosisKeysTransaction.start(registrationToken)
SubmitDiagnosisKeysTransaction.start(authCode)
deleteAuthCode()
} }
fun validateAndStoreTestGUID(testGUID: String) { fun validateAndStoreTestGUID(testGUID: String) {
......
...@@ -2,13 +2,15 @@ package de.rki.coronawarnapp.transaction ...@@ -2,13 +2,15 @@ package de.rki.coronawarnapp.transaction
import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
import de.rki.coronawarnapp.service.diagnosiskey.DiagnosisKeyService import de.rki.coronawarnapp.service.diagnosiskey.DiagnosisKeyService
import de.rki.coronawarnapp.service.submission.SubmissionService
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.CLOSE import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.CLOSE
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TAN
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.SUBMIT_KEYS import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.SUBMIT_KEYS
import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.transformKeyHistoryToExternalFormat import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.transformKeyHistoryToExternalFormat
/** /**
* The ReportDiagnosisKeysTransaction is used to define an atomic Transaction for Key Reports. Its states allow an * The SubmitDiagnosisKeysTransaction is used to define an atomic Transaction for Key Reports. Its states allow an
* isolated work area that can recover from failures and keep a consistent key state even through an * isolated work area that can recover from failures and keep a consistent key state even through an
* unclear, potentially dangerous state within the transaction itself. It is guaranteed that the Key Files * unclear, potentially dangerous state within the transaction itself. It is guaranteed that the Key Files
* that are used in the transaction will be generated, submitted and accepted from the Google API once the transaction * that are used in the transaction will be generated, submitted and accepted from the Google API once the transaction
...@@ -17,11 +19,10 @@ import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.transformKeyHist ...@@ -17,11 +19,10 @@ import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.transformKeyHist
* There is currently a simple rollback behavior needed / identified. * There is currently a simple rollback behavior needed / identified.
* *
* The Transaction undergoes multiple States: * The Transaction undergoes multiple States:
* 1. INIT - Initial Setup of the Transaction and Transaction ID Generation * 1. RETRIEVE_TAN - Fetch the TAN with the provided Registration Token
* 2. TOKEN - Initialisation of the identifying token used during the entire transaction * 2. RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY - Get the TEKs from the exposure notification framework
* 3. RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY - Retrieval of the Exposure Key History from the API * 3. SUBMIT_KEYS - Submission of the diagnosis keys to the Server
* 5. SUBMIT_KEYS - Submission of Key retrieved to the Server * 4. CLOSE - Transaction Closure
* 8. CLOSE - Transaction Closure
* *
* This transaction is special in terms of concurrent entry-calls (e.g. calling the transaction again before it closes and * This transaction is special in terms of concurrent entry-calls (e.g. calling the transaction again before it closes and
* releases its internal mutex. The transaction will not queue up like a normal mutex, but instead completely omit the last * releases its internal mutex. The transaction will not queue up like a normal mutex, but instead completely omit the last
...@@ -38,13 +39,20 @@ object SubmitDiagnosisKeysTransaction : Transaction() { ...@@ -38,13 +39,20 @@ object SubmitDiagnosisKeysTransaction : Transaction() {
/** possible transaction states */ /** possible transaction states */
private enum class SubmitDiagnosisKeysTransactionState : private enum class SubmitDiagnosisKeysTransactionState :
TransactionState { TransactionState {
RETRIEVE_TAN,
RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY, RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY,
SUBMIT_KEYS, SUBMIT_KEYS,
CLOSE CLOSE
} }
/** initiates the transaction. This suspend function guarantees a successful transaction once completed. */ /** initiates the transaction. This suspend function guarantees a successful transaction once completed. */
suspend fun start(authCode: String) = lockAndExecuteUnique { suspend fun start(registrationToken: String) = lockAndExecuteUnique {
/****************************************************
* RETRIEVE TAN
****************************************************/
val authCode = executeState(RETRIEVE_TAN) {
SubmissionService.asyncRequestAuthCode()
}
/**************************************************** /****************************************************
* RETRIEVE TEMPORARY EXPOSURE KEY HISTORY * RETRIEVE TEMPORARY EXPOSURE KEY HISTORY
****************************************************/ ****************************************************/
......
...@@ -4,15 +4,16 @@ import android.os.Bundle ...@@ -4,15 +4,16 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import de.rki.coronawarnapp.R import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
import de.rki.coronawarnapp.databinding.FragmentSubmissionPositiveOtherWarningBinding import de.rki.coronawarnapp.databinding.FragmentSubmissionPositiveOtherWarningBinding
import de.rki.coronawarnapp.nearby.InternalExposureNotificationPermissionHelper
import de.rki.coronawarnapp.ui.BaseFragment import de.rki.coronawarnapp.ui.BaseFragment
import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
class SubmissionResultPositiveOtherWarningFragment : BaseFragment() { class SubmissionResultPositiveOtherWarningFragment : BaseFragment(),
InternalExposureNotificationPermissionHelper.Callback {
companion object { companion object {
private val TAG: String? = SubmissionResultPositiveOtherWarningFragment::class.simpleName private val TAG: String? = SubmissionResultPositiveOtherWarningFragment::class.simpleName
...@@ -20,12 +21,34 @@ class SubmissionResultPositiveOtherWarningFragment : BaseFragment() { ...@@ -20,12 +21,34 @@ class SubmissionResultPositiveOtherWarningFragment : BaseFragment() {
private val viewModel: SubmissionViewModel by activityViewModels() private val viewModel: SubmissionViewModel by activityViewModels()
private lateinit var binding: FragmentSubmissionPositiveOtherWarningBinding private lateinit var binding: FragmentSubmissionPositiveOtherWarningBinding
private var submissionRequested = false
private var submissionFailed = false
private lateinit var internalExposureNotificationPermissionHelper:
InternalExposureNotificationPermissionHelper
override fun onResume() {
super.onResume()
if (submissionRequested && !submissionFailed) {
internalExposureNotificationPermissionHelper.requestPermissionToShareKeys()
}
}
override fun onKeySharePermissionGranted(keys: List<TemporaryExposureKey>) {
super.onKeySharePermissionGranted(keys)
viewModel.submitDiagnosisKeys()
}
override fun onFailure(exception: Exception?) {
submissionFailed = true
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
internalExposureNotificationPermissionHelper =
InternalExposureNotificationPermissionHelper(this, this)
binding = FragmentSubmissionPositiveOtherWarningBinding.inflate(inflater) binding = FragmentSubmissionPositiveOtherWarningBinding.inflate(inflater)
binding.lifecycleOwner = this binding.lifecycleOwner = this
return binding.root return binding.root
...@@ -35,13 +58,6 @@ class SubmissionResultPositiveOtherWarningFragment : BaseFragment() { ...@@ -35,13 +58,6 @@ class SubmissionResultPositiveOtherWarningFragment : BaseFragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setButtonOnClickListener() setButtonOnClickListener()
// TODO Maybe move this to a discrete transaction containing both steps
viewModel.authCodeState.observe(viewLifecycleOwner, Observer {
if (it == ApiRequestState.SUCCESS) {
viewModel.submitDiagnosisKeys()
}
})
viewModel.submissionState.observe(viewLifecycleOwner, Observer { viewModel.submissionState.observe(viewLifecycleOwner, Observer {
if (it == ApiRequestState.SUCCESS) { if (it == ApiRequestState.SUCCESS) {
doNavigate( doNavigate(
...@@ -54,7 +70,8 @@ class SubmissionResultPositiveOtherWarningFragment : BaseFragment() { ...@@ -54,7 +70,8 @@ class SubmissionResultPositiveOtherWarningFragment : BaseFragment() {
private fun setButtonOnClickListener() { private fun setButtonOnClickListener() {
binding.submissionPositiveOtherWarningButton.setOnClickListener { binding.submissionPositiveOtherWarningButton.setOnClickListener {
showShareIDConfirmationDialog() submissionRequested = true
internalExposureNotificationPermissionHelper.requestPermissionToShareKeys()
} }
binding.submissionPositiveOtherWarningHeader binding.submissionPositiveOtherWarningHeader
.informationHeader.headerButtonBack.buttonIcon.setOnClickListener { .informationHeader.headerButtonBack.buttonIcon.setOnClickListener {
...@@ -64,24 +81,4 @@ class SubmissionResultPositiveOtherWarningFragment : BaseFragment() { ...@@ -64,24 +81,4 @@ class SubmissionResultPositiveOtherWarningFragment : BaseFragment() {
) )
} }
} }
private fun showShareIDConfirmationDialog() {
val alertDialog: AlertDialog = requireActivity().let {
val builder = AlertDialog.Builder(it)
builder.apply {
setTitle(R.string.submission_positive_dialog_confirmation_title)
setMessage(R.string.submission_positive_dialog_confirmation_body)
setPositiveButton(
R.string.submission_positive_dialog_confirmation_positive
) { _, _ ->
viewModel.requestAuthCode()
}
setNegativeButton(
R.string.submission_positive_dialog_confirmation_negative
) { _, _ -> }
}
builder.create()
}
alertDialog.show()
}
} }
...@@ -38,9 +38,6 @@ class SubmissionViewModel : ViewModel() { ...@@ -38,9 +38,6 @@ class SubmissionViewModel : ViewModel() {
fun submitDiagnosisKeys() = fun submitDiagnosisKeys() =
executeRequestWithState(SubmissionService::asyncSubmitExposureKeys, _submissionState) executeRequestWithState(SubmissionService::asyncSubmitExposureKeys, _submissionState)
fun requestAuthCode() =
executeRequestWithState(SubmissionService::asyncRequestAuthCode, _authCodeState)
fun doDeviceRegistration() = fun doDeviceRegistration() =
executeRequestWithState(SubmissionService::asyncRegisterDevice, _registrationState) executeRequestWithState(SubmissionService::asyncRegisterDevice, _registrationState)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment