From 16f016a99b971d89e9f4c988c4ce8f7421f9983d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakob=20M=C3=B6ller?= <jakob.moeller@sap.com>
Date: Sat, 6 Jun 2020 22:38:09 +0200
Subject: [PATCH] Complex HTTP Errors (#213)

* Complex HTTP Errors

Signed-off-by: d067928 <jakob.moeller@sap.com>

* Correct Boolean Logic for Client/Server Error Check

Signed-off-by: d067928 <jakob.moeller@sap.com>

* Moved error handling of submission flow to CwaWebExceptions (#220)

Co-authored-by: Kolya Opahle <k.opahle@sap.com>
---
 Corona-Warn-App/config/detekt.yml             | 14 +++-
 .../SubmissionTanInvalidException.kt          | 28 --------
 .../exception/TestAlreadyPairedException.kt   | 28 --------
 .../exception/TestPairingInvalidException.kt  | 28 --------
 .../exception/WebRequestException.kt          | 28 --------
 .../exception/http/CwaWebException.kt         | 44 +++++++++++++
 .../rki/coronawarnapp/http/HttpErrorParser.kt | 64 +++++++++++++++++++
 .../rki/coronawarnapp/http/ServiceFactory.kt  |  3 +-
 .../diagnosiskey/DiagnosisKeyService.kt       | 25 ++------
 .../service/submission/SubmissionService.kt   | 42 +++---------
 .../SubmissionQRCodeScanFragment.kt           | 26 ++++++--
 ...ssionResultPositiveOtherWarningFragment.kt | 27 ++++++--
 .../ui/submission/SubmissionTanFragment.kt    | 26 ++++++--
 .../SubmissionTestResultFragment.kt           | 22 +++++--
 .../ui/viewmodel/SubmissionViewModel.kt       | 27 +++++---
 15 files changed, 231 insertions(+), 201 deletions(-)
 delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/SubmissionTanInvalidException.kt
 delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/TestAlreadyPairedException.kt
 delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/TestPairingInvalidException.kt
 delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/WebRequestException.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/http/CwaWebException.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/HttpErrorParser.kt

diff --git a/Corona-Warn-App/config/detekt.yml b/Corona-Warn-App/config/detekt.yml
index 9cad3798d..16a273985 100644
--- a/Corona-Warn-App/config/detekt.yml
+++ b/Corona-Warn-App/config/detekt.yml
@@ -66,7 +66,7 @@ complexity:
     active: true
     threshold: 15
     ignoreSingleWhenExpression: false
-    ignoreSimpleWhenEntries: false
+    ignoreSimpleWhenEntries: true
     ignoreNestingFunctions: false
     nestingFunctions: [run, let, apply, with, also, use, forEach, isNotNull, ifNull]
   LabeledExpression:
@@ -514,7 +514,15 @@ style:
     maxJumpCount: 1
   MagicNumber:
     active: true
-    excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
+    excludes: [
+      '**/test/**',
+      '**/androidTest/**',
+      '**/*.Test.kt',
+      '**/*.Spec.kt',
+      '**/*.Spek.kt',
+      '**/CwaWebException.kt',
+      '**/HttpErrorParser.kt'
+    ]
     ignoreNumbers: ['-1', '0', '1', '2']
     ignoreHashCodeFunction: true
     ignorePropertyDeclaration: false
@@ -523,7 +531,7 @@ style:
     ignoreCompanionObjectPropertyDeclaration: true
     ignoreAnnotation: false
     ignoreNamedArgument: true
-    ignoreEnums: false
+    ignoreEnums: true
     ignoreRanges: false
   MandatoryBracesIfStatements:
     active: false
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/SubmissionTanInvalidException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/SubmissionTanInvalidException.kt
deleted file mode 100644
index 55b2acb7c..000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/SubmissionTanInvalidException.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/******************************************************************************
- * Corona-Warn-App                                                            *
- *                                                                            *
- * SAP SE and all other contributors /                                        *
- * copyright owners license this file to you under the Apache                 *
- * License, Version 2.0 (the "License"); you may not use this                 *
- * file except in compliance with the License.                                *
- * You may obtain a copy of the License at                                    *
- *                                                                            *
- * http://www.apache.org/licenses/LICENSE-2.0                                 *
- *                                                                            *
- * Unless required by applicable law or agreed to in writing,                 *
- * software distributed under the License is distributed on an                *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY                     *
- * KIND, either express or implied.  See the License for the                  *
- * specific language governing permissions and limitations                    *
- * under the License.                                                         *
- ******************************************************************************/
-
-package de.rki.coronawarnapp.exception
-
-/**
- * An Exception thrown when an error occurs inside the [de.rki.coronawarnapp.http.WebRequestBuilder]
- *
- * @param message an Exception thrown inside the WebRequestBuilder
- * @param cause the cause of the error
- */
-class SubmissionTanInvalidException(message: String, cause: Throwable) : Exception(message, cause)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/TestAlreadyPairedException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/TestAlreadyPairedException.kt
deleted file mode 100644
index b2af0b017..000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/TestAlreadyPairedException.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/******************************************************************************
- * Corona-Warn-App                                                            *
- *                                                                            *
- * SAP SE and all other contributors /                                        *
- * copyright owners license this file to you under the Apache                 *
- * License, Version 2.0 (the "License"); you may not use this                 *
- * file except in compliance with the License.                                *
- * You may obtain a copy of the License at                                    *
- *                                                                            *
- * http://www.apache.org/licenses/LICENSE-2.0                                 *
- *                                                                            *
- * Unless required by applicable law or agreed to in writing,                 *
- * software distributed under the License is distributed on an                *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY                     *
- * KIND, either express or implied.  See the License for the                  *
- * specific language governing permissions and limitations                    *
- * under the License.                                                         *
- ******************************************************************************/
-
-package de.rki.coronawarnapp.exception
-
-/**
- * An Exception thrown when an error occurs inside the [de.rki.coronawarnapp.http.WebRequestBuilder]
- *
- * @param message an Exception thrown inside the WebRequestBuilder
- * @param cause the cause of the error
- */
-class TestAlreadyPairedException(message: String, cause: Throwable) : Exception(message, cause)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/TestPairingInvalidException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/TestPairingInvalidException.kt
deleted file mode 100644
index 776194407..000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/TestPairingInvalidException.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/******************************************************************************
- * Corona-Warn-App                                                            *
- *                                                                            *
- * SAP SE and all other contributors /                                        *
- * copyright owners license this file to you under the Apache                 *
- * License, Version 2.0 (the "License"); you may not use this                 *
- * file except in compliance with the License.                                *
- * You may obtain a copy of the License at                                    *
- *                                                                            *
- * http://www.apache.org/licenses/LICENSE-2.0                                 *
- *                                                                            *
- * Unless required by applicable law or agreed to in writing,                 *
- * software distributed under the License is distributed on an                *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY                     *
- * KIND, either express or implied.  See the License for the                  *
- * specific language governing permissions and limitations                    *
- * under the License.                                                         *
- ******************************************************************************/
-
-package de.rki.coronawarnapp.exception
-
-/**
- * An Exception thrown when an error occurs inside the [de.rki.coronawarnapp.http.WebRequestBuilder]
- *
- * @param message an Exception thrown inside the WebRequestBuilder
- * @param cause the cause of the error
- */
-class TestPairingInvalidException(message: String, cause: Throwable) : Exception(message, cause)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/WebRequestException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/WebRequestException.kt
deleted file mode 100644
index bb9d4442e..000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/WebRequestException.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/******************************************************************************
- * Corona-Warn-App                                                            *
- *                                                                            *
- * SAP SE and all other contributors /                                        *
- * copyright owners license this file to you under the Apache                 *
- * License, Version 2.0 (the "License"); you may not use this                 *
- * file except in compliance with the License.                                *
- * You may obtain a copy of the License at                                    *
- *                                                                            *
- * http://www.apache.org/licenses/LICENSE-2.0                                 *
- *                                                                            *
- * Unless required by applicable law or agreed to in writing,                 *
- * software distributed under the License is distributed on an                *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY                     *
- * KIND, either express or implied.  See the License for the                  *
- * specific language governing permissions and limitations                    *
- * under the License.                                                         *
- ******************************************************************************/
-
-package de.rki.coronawarnapp.exception
-
-/**
- * An Exception thrown when an error occurs inside the [de.rki.coronawarnapp.http.WebRequestBuilder]
- *
- * @param message an Exception thrown inside the WebRequestBuilder
- * @param cause the cause of the error
- */
-class WebRequestException(message: String, cause: Throwable) : Exception(message, cause)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/http/CwaWebException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/http/CwaWebException.kt
new file mode 100644
index 000000000..fd628417f
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/http/CwaWebException.kt
@@ -0,0 +1,44 @@
+package de.rki.coronawarnapp.exception.http
+
+import java.io.IOException
+
+open class CwaWebException(statusCode: Int) : IOException(
+    "error during web request, http status $statusCode"
+)
+
+open class CwaServerError(val statusCode: Int) : CwaWebException(statusCode) {
+    init {
+        if (statusCode !in 500..599)
+            throw IllegalArgumentException("a server error has to have code 5xx")
+    }
+}
+
+open class CwaClientError(val statusCode: Int) : CwaWebException(statusCode) {
+    init {
+        if (statusCode !in 400..499)
+            throw IllegalArgumentException("a client error has to have code 4xx")
+    }
+}
+
+open class CwaSuccessResponseWithCodeMismatchNotSupportedError(val statusCode: Int) : CwaWebException(statusCode)
+open class CwaInformationalNotSupportedError(val statusCode: Int) : CwaWebException(statusCode)
+open class CwaRedirectNotSupportedError(val statusCode: Int) : CwaWebException(statusCode)
+
+class BadRequestException : CwaClientError(400)
+class UnauthorizedException : CwaClientError(401)
+class ForbiddenException : CwaClientError(403)
+class NotFoundException : CwaClientError(404)
+class ConflictException : CwaClientError(409)
+class GoneException : CwaClientError(410)
+class UnsupportedMediaTypeException : CwaClientError(415)
+class TooManyRequestsException : CwaClientError(429)
+
+class InternalServerErrorException : CwaServerError(500)
+class NotImplementedException : CwaServerError(501)
+class BadGatewayException : CwaServerError(502)
+class ServiceUnavailableException : CwaServerError(503)
+class GatewayTimeoutException : CwaServerError(504)
+class HTTPVersionNotSupported : CwaServerError(505)
+class NetworkAuthenticationRequiredException : CwaServerError(511)
+class NetworkReadTimeoutException : CwaServerError(598)
+class NetworkConnectTimeoutException : CwaServerError(599)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/HttpErrorParser.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/HttpErrorParser.kt
new file mode 100644
index 000000000..660017005
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/HttpErrorParser.kt
@@ -0,0 +1,64 @@
+package de.rki.coronawarnapp.http
+
+import de.rki.coronawarnapp.exception.http.BadGatewayException
+import de.rki.coronawarnapp.exception.http.CwaWebException
+import de.rki.coronawarnapp.exception.http.BadRequestException
+import de.rki.coronawarnapp.exception.http.ConflictException
+import de.rki.coronawarnapp.exception.http.CwaClientError
+import de.rki.coronawarnapp.exception.http.CwaInformationalNotSupportedError
+import de.rki.coronawarnapp.exception.http.CwaRedirectNotSupportedError
+import de.rki.coronawarnapp.exception.http.CwaServerError
+import de.rki.coronawarnapp.exception.http.CwaSuccessResponseWithCodeMismatchNotSupportedError
+import de.rki.coronawarnapp.exception.http.ForbiddenException
+import de.rki.coronawarnapp.exception.http.GatewayTimeoutException
+import de.rki.coronawarnapp.exception.http.GoneException
+import de.rki.coronawarnapp.exception.http.HTTPVersionNotSupported
+import de.rki.coronawarnapp.exception.http.InternalServerErrorException
+import de.rki.coronawarnapp.exception.http.NetworkAuthenticationRequiredException
+import de.rki.coronawarnapp.exception.http.NetworkConnectTimeoutException
+import de.rki.coronawarnapp.exception.http.NetworkReadTimeoutException
+import de.rki.coronawarnapp.exception.http.NotFoundException
+import de.rki.coronawarnapp.exception.http.NotImplementedException
+import de.rki.coronawarnapp.exception.http.ServiceUnavailableException
+import de.rki.coronawarnapp.exception.http.TooManyRequestsException
+import de.rki.coronawarnapp.exception.http.UnauthorizedException
+import de.rki.coronawarnapp.exception.http.UnsupportedMediaTypeException
+import okhttp3.Interceptor
+import okhttp3.Response
+
+class HttpErrorParser : Interceptor {
+    override fun intercept(chain: Interceptor.Chain): Response {
+        val response = chain.proceed(chain.request())
+        return when (val code = response.code) {
+            200 -> response
+            201 -> response
+            202 -> response
+            204 -> response
+            400 -> throw BadRequestException()
+            401 -> throw UnauthorizedException()
+            403 -> throw ForbiddenException()
+            404 -> throw NotFoundException()
+            409 -> throw ConflictException()
+            410 -> throw GoneException()
+            415 -> throw UnsupportedMediaTypeException()
+            429 -> throw TooManyRequestsException()
+            500 -> throw InternalServerErrorException()
+            501 -> throw NotImplementedException()
+            502 -> throw BadGatewayException()
+            503 -> throw ServiceUnavailableException()
+            504 -> throw GatewayTimeoutException()
+            505 -> throw HTTPVersionNotSupported()
+            511 -> throw NetworkAuthenticationRequiredException()
+            598 -> throw NetworkReadTimeoutException()
+            599 -> throw NetworkConnectTimeoutException()
+            else -> {
+                if (code in 100..199) throw CwaInformationalNotSupportedError(code)
+                if (code in 200..299) throw CwaSuccessResponseWithCodeMismatchNotSupportedError(code)
+                if (code in 300..399) throw CwaRedirectNotSupportedError(code)
+                if (code in 400..499) throw CwaClientError(code)
+                if (code in 500..599) throw CwaServerError(code)
+                throw CwaWebException(code)
+            }
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/ServiceFactory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/ServiceFactory.kt
index b17803daa..90c2fd6fc 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/ServiceFactory.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/ServiceFactory.kt
@@ -39,7 +39,8 @@ class ServiceFactory {
             if (BuildConfig.DEBUG) it.setLevel(HttpLoggingInterceptor.Level.BODY)
         },
         OfflineCacheInterceptor(CoronaWarnApplication.getAppContext()),
-        RetryInterceptor()
+        RetryInterceptor(),
+        HttpErrorParser()
     )
 
     /**
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/diagnosiskey/DiagnosisKeyService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/diagnosiskey/DiagnosisKeyService.kt
index e30de86ea..fc4cc03da 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/diagnosiskey/DiagnosisKeyService.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/diagnosiskey/DiagnosisKeyService.kt
@@ -23,10 +23,7 @@ import KeyExportFormat
 import android.util.Log
 import de.rki.coronawarnapp.exception.DiagnosisKeyRetrievalException
 import de.rki.coronawarnapp.exception.DiagnosisKeySubmissionException
-import de.rki.coronawarnapp.exception.SubmissionTanInvalidException
 import de.rki.coronawarnapp.http.WebRequestBuilder
-import de.rki.coronawarnapp.service.diagnosiskey.DiagnosisKeyConstants.SERVER_ERROR_CODE_403
-import retrofit2.HttpException
 
 /**
  * The Diagnosis Key Service is used to interact with the Server to submit and retrieve keys through
@@ -50,21 +47,11 @@ object DiagnosisKeyService {
      * @param keysToReport - KeyList in the Server Format to submit to the Server
      */
     suspend fun asyncSubmitKeys(authCode: String, keysToReport: List<KeyExportFormat.TemporaryExposureKey>) {
-        try {
-            Log.d(TAG, "Diagnosis Keys will be submitted.")
-            WebRequestBuilder.asyncSubmitKeysToServer(
-                authCode,
-                false,
-                keysToReport
-            )
-        } catch (e: HttpException) {
-            if (e.code() == SERVER_ERROR_CODE_403) {
-                throw SubmissionTanInvalidException(
-                    "the test paring to the device is invalid",
-                    e
-                )
-            }
-            throw e
-        }
+        Log.d(TAG, "Diagnosis Keys will be submitted.")
+        WebRequestBuilder.asyncSubmitKeysToServer(
+            authCode,
+            false,
+            keysToReport
+        )
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt
index fc3f32667..6cdd39fee 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt
@@ -2,38 +2,24 @@ package de.rki.coronawarnapp.service.submission
 
 import de.rki.coronawarnapp.exception.NoGUIDOrTANSetException
 import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
-import de.rki.coronawarnapp.exception.TestAlreadyPairedException
-import de.rki.coronawarnapp.exception.TestPairingInvalidException
 import de.rki.coronawarnapp.http.WebRequestBuilder
 import de.rki.coronawarnapp.service.submission.SubmissionConstants.QR_CODE_KEY_TYPE
-import de.rki.coronawarnapp.service.submission.SubmissionConstants.SERVER_ERROR_CODE_400
 import de.rki.coronawarnapp.service.submission.SubmissionConstants.TELE_TAN_KEY_TYPE
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction
 import de.rki.coronawarnapp.util.formatter.TestResult
-import retrofit2.HttpException
 
 object SubmissionService {
     suspend fun asyncRegisterDevice() {
-        try {
-            val testGUID = LocalData.testGUID()
-            val testTAN = LocalData.teletan()
-
-            when {
-                testGUID != null -> asyncRegisterDeviceViaGUID(testGUID)
-                testTAN != null -> asyncRegisterDeviceViaTAN(testTAN)
-                else -> throw NoGUIDOrTANSetException()
-            }
-            LocalData.devicePairingSuccessfulTimestamp(System.currentTimeMillis())
-        } catch (err: HttpException) {
-            if (err.code() == SERVER_ERROR_CODE_400) {
-                throw TestAlreadyPairedException(
-                    "the test was already paired to a different device",
-                    err
-                )
-            }
-            throw err
+        val testGUID = LocalData.testGUID()
+        val testTAN = LocalData.teletan()
+
+        when {
+            testGUID != null -> asyncRegisterDeviceViaGUID(testGUID)
+            testTAN != null -> asyncRegisterDeviceViaTAN(testTAN)
+            else -> throw NoGUIDOrTANSetException()
         }
+        LocalData.devicePairingSuccessfulTimestamp(System.currentTimeMillis())
     }
 
     private suspend fun asyncRegisterDeviceViaGUID(guid: String) {
@@ -59,17 +45,7 @@ object SubmissionService {
     }
 
     suspend fun asyncRequestAuthCode(registrationToken: String): String {
-        try {
-            return WebRequestBuilder.asyncGetTan(registrationToken)
-        } catch (err: HttpException) {
-            if (err.code() == SERVER_ERROR_CODE_400) {
-                throw TestPairingInvalidException(
-                    "the test paring to the device is invalid",
-                    err
-                )
-            }
-            throw err
-        }
+        return WebRequestBuilder.asyncGetTan(registrationToken)
     }
 
     suspend fun asyncSubmitExposureKeys() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionQRCodeScanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionQRCodeScanFragment.kt
index 97d467a1f..0b3a2ccef 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionQRCodeScanFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionQRCodeScanFragment.kt
@@ -11,14 +11,16 @@ import com.journeyapps.barcodescanner.BarcodeResult
 import com.journeyapps.barcodescanner.DefaultDecoderFactory
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionQrCodeScanBinding
-import de.rki.coronawarnapp.exception.TestAlreadyPairedException
+import de.rki.coronawarnapp.exception.http.BadRequestException
+import de.rki.coronawarnapp.exception.http.CwaClientError
+import de.rki.coronawarnapp.exception.http.CwaServerError
+import de.rki.coronawarnapp.exception.http.CwaWebException
 import de.rki.coronawarnapp.ui.BaseFragment
 import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.util.CameraPermissionHelper
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.observeEvent
-import retrofit2.HttpException
 
 /**
  * A simple [BaseFragment] subclass.
@@ -57,9 +59,9 @@ class SubmissionQRCodeScanFragment : BaseFragment() {
         _binding = null
     }
 
-    private fun buildErrorDialog(exception: Exception): DialogHelper.DialogInstance {
+    private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance {
         return when (exception) {
-            is TestAlreadyPairedException -> DialogHelper.DialogInstance(
+            is BadRequestException -> DialogHelper.DialogInstance(
                 requireActivity(),
                 R.string.submission_error_dialog_web_test_paired_title,
                 R.string.submission_error_dialog_web_test_paired_body,
@@ -68,12 +70,24 @@ class SubmissionQRCodeScanFragment : BaseFragment() {
                 true,
                 ::navigateToDispatchScreen
             )
-            is HttpException -> DialogHelper.DialogInstance(
+            is CwaServerError -> DialogHelper.DialogInstance(
                 requireActivity(),
                 R.string.submission_error_dialog_web_generic_error_title,
                 getString(
                     R.string.submission_error_dialog_web_generic_network_error_body,
-                    exception.code()
+                    exception.statusCode
+                ),
+                R.string.submission_error_dialog_web_generic_error_button_positive,
+                null,
+                true,
+                ::navigateToDispatchScreen
+            )
+            is CwaClientError -> DialogHelper.DialogInstance(
+                requireActivity(),
+                R.string.submission_error_dialog_web_generic_error_title,
+                getString(
+                    R.string.submission_error_dialog_web_generic_network_error_body,
+                    exception.statusCode
                 ),
                 R.string.submission_error_dialog_web_generic_error_button_positive,
                 null,
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionResultPositiveOtherWarningFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionResultPositiveOtherWarningFragment.kt
index 477722597..028722347 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionResultPositiveOtherWarningFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionResultPositiveOtherWarningFragment.kt
@@ -8,15 +8,16 @@ import androidx.fragment.app.activityViewModels
 import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionPositiveOtherWarningBinding
-import de.rki.coronawarnapp.exception.SubmissionTanInvalidException
-import de.rki.coronawarnapp.exception.TestPairingInvalidException
+import de.rki.coronawarnapp.exception.http.BadRequestException
+import de.rki.coronawarnapp.exception.http.CwaClientError
+import de.rki.coronawarnapp.exception.http.CwaServerError
+import de.rki.coronawarnapp.exception.http.ForbiddenException
 import de.rki.coronawarnapp.nearby.InternalExposureNotificationPermissionHelper
 import de.rki.coronawarnapp.ui.BaseFragment
 import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.observeEvent
-import retrofit2.HttpException
 
 class SubmissionResultPositiveOtherWarningFragment : BaseFragment(),
     InternalExposureNotificationPermissionHelper.Callback {
@@ -71,7 +72,7 @@ class SubmissionResultPositiveOtherWarningFragment : BaseFragment(),
 
     private fun buildErrorDialog(exception: Exception): DialogHelper.DialogInstance {
         return when (exception) {
-            is TestPairingInvalidException -> DialogHelper.DialogInstance(
+            is BadRequestException -> DialogHelper.DialogInstance(
                 requireActivity(),
                 R.string.submission_error_dialog_web_paring_invalid_title,
                 R.string.submission_error_dialog_web_paring_invalid_body,
@@ -80,7 +81,7 @@ class SubmissionResultPositiveOtherWarningFragment : BaseFragment(),
                 true,
                 ::navigateToSubmissionResultFragment
             )
-            is SubmissionTanInvalidException -> DialogHelper.DialogInstance(
+            is ForbiddenException -> DialogHelper.DialogInstance(
                 requireActivity(),
                 R.string.submission_error_dialog_web_tan_invalid_title,
                 R.string.submission_error_dialog_web_tan_invalid_body,
@@ -89,12 +90,24 @@ class SubmissionResultPositiveOtherWarningFragment : BaseFragment(),
                 true,
                 ::navigateToSubmissionResultFragment
             )
-            is HttpException -> DialogHelper.DialogInstance(
+            is CwaServerError -> DialogHelper.DialogInstance(
                 requireActivity(),
                 R.string.submission_error_dialog_web_generic_error_title,
                 getString(
                     R.string.submission_error_dialog_web_generic_network_error_body,
-                    exception.code()
+                    exception.statusCode
+                ),
+                R.string.submission_error_dialog_web_generic_error_button_positive,
+                null,
+                true,
+                ::navigateToSubmissionResultFragment
+            )
+            is CwaClientError -> DialogHelper.DialogInstance(
+                requireActivity(),
+                R.string.submission_error_dialog_web_generic_error_title,
+                getString(
+                    R.string.submission_error_dialog_web_generic_network_error_body,
+                    exception.statusCode
                 ),
                 R.string.submission_error_dialog_web_generic_error_button_positive,
                 null,
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragment.kt
index 56b4c4608..628771094 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragment.kt
@@ -7,12 +7,14 @@ import android.view.ViewGroup
 import androidx.fragment.app.activityViewModels
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionTanBinding
-import de.rki.coronawarnapp.exception.TestAlreadyPairedException
+import de.rki.coronawarnapp.exception.http.BadRequestException
+import de.rki.coronawarnapp.exception.http.CwaClientError
+import de.rki.coronawarnapp.exception.http.CwaServerError
+import de.rki.coronawarnapp.exception.http.CwaWebException
 import de.rki.coronawarnapp.ui.BaseFragment
 import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.observeEvent
-import retrofit2.HttpException
 
 /**
  * Fragment for TAN entry
@@ -41,9 +43,9 @@ class SubmissionTanFragment : BaseFragment() {
         _binding = null
     }
 
-    private fun buildErrorDialog(exception: Exception): DialogHelper.DialogInstance {
+    private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance {
         return when (exception) {
-            is TestAlreadyPairedException -> DialogHelper.DialogInstance(
+            is BadRequestException -> DialogHelper.DialogInstance(
                 requireActivity(),
                 R.string.submission_error_dialog_web_test_paired_title,
                 R.string.submission_error_dialog_web_test_paired_body,
@@ -52,12 +54,24 @@ class SubmissionTanFragment : BaseFragment() {
                 true,
                 ::navigateToDispatchScreen
             )
-            is HttpException -> DialogHelper.DialogInstance(
+            is CwaServerError -> DialogHelper.DialogInstance(
                 requireActivity(),
                 R.string.submission_error_dialog_web_generic_error_title,
                 getString(
                     R.string.submission_error_dialog_web_generic_network_error_body,
-                    exception.code()
+                    exception.statusCode
+                ),
+                R.string.submission_error_dialog_web_generic_error_button_positive,
+                null,
+                true,
+                ::navigateToDispatchScreen
+            )
+            is CwaClientError -> DialogHelper.DialogInstance(
+                requireActivity(),
+                R.string.submission_error_dialog_web_generic_error_title,
+                getString(
+                    R.string.submission_error_dialog_web_generic_network_error_body,
+                    exception.statusCode
                 ),
                 R.string.submission_error_dialog_web_generic_error_button_positive,
                 null,
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragment.kt
index c0b07dfed..3037b5b61 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragment.kt
@@ -7,12 +7,14 @@ import android.view.ViewGroup
 import androidx.fragment.app.activityViewModels
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionTestResultBinding
+import de.rki.coronawarnapp.exception.http.CwaClientError
+import de.rki.coronawarnapp.exception.http.CwaServerError
+import de.rki.coronawarnapp.exception.http.CwaWebException
 import de.rki.coronawarnapp.ui.BaseFragment
 import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.observeEvent
-import retrofit2.HttpException
 
 /**
  * A simple [BaseFragment] subclass.
@@ -44,14 +46,26 @@ class SubmissionTestResultFragment : BaseFragment() {
     private fun navigateToMainScreen() =
         doNavigate(SubmissionTestResultFragmentDirections.actionSubmissionResultFragmentToMainFragment())
 
-    private fun buildErrorDialog(exception: Exception): DialogHelper.DialogInstance {
+    private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance {
         return when (exception) {
-            is HttpException -> DialogHelper.DialogInstance(
+            is CwaServerError -> DialogHelper.DialogInstance(
                 requireActivity(),
                 R.string.submission_error_dialog_web_generic_error_title,
                 getString(
                     R.string.submission_error_dialog_web_generic_network_error_body,
-                    exception.code()
+                    exception.statusCode
+                ),
+                R.string.submission_error_dialog_web_generic_error_button_positive,
+                null,
+                true,
+                ::navigateToMainScreen
+            )
+            is CwaClientError -> DialogHelper.DialogInstance(
+                requireActivity(),
+                R.string.submission_error_dialog_web_generic_error_title,
+                getString(
+                    R.string.submission_error_dialog_web_generic_network_error_body,
+                    exception.statusCode
                 ),
                 R.string.submission_error_dialog_web_generic_error_button_positive,
                 null,
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt
index 39431e54f..60ab8b5cd 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt
@@ -4,6 +4,9 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import de.rki.coronawarnapp.exception.ExceptionCategory
+import de.rki.coronawarnapp.exception.http.CwaWebException
+import de.rki.coronawarnapp.exception.report
 import de.rki.coronawarnapp.service.submission.SubmissionService
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.storage.SubmissionRepository
@@ -18,24 +21,24 @@ class SubmissionViewModel : ViewModel() {
     private val _scanStatus = MutableLiveData(Event(ScanStatus.STARTED))
 
     private val _registrationState = MutableLiveData(Event(ApiRequestState.IDLE))
-    private val _registrationError = MutableLiveData<Event<Exception>>(null)
+    private val _registrationError = MutableLiveData<Event<CwaWebException>>(null)
 
     private val _uiStateState = MutableLiveData(ApiRequestState.IDLE)
-    private val _uiStateError = MutableLiveData<Event<Exception>>(null)
+    private val _uiStateError = MutableLiveData<Event<CwaWebException>>(null)
 
     private val _submissionState = MutableLiveData(Event(ApiRequestState.IDLE))
-    private val _submissionError = MutableLiveData<Event<Exception>>(null)
+    private val _submissionError = MutableLiveData<Event<CwaWebException>>(null)
 
     val scanStatus: LiveData<Event<ScanStatus>> = _scanStatus
 
     val registrationState: LiveData<Event<ApiRequestState>> = _registrationState
-    val registrationError: LiveData<Event<Exception>> = _registrationError
+    val registrationError: LiveData<Event<CwaWebException>> = _registrationError
 
     val uiStateState: LiveData<ApiRequestState> = _uiStateState
-    val uiStateError: LiveData<Event<Exception>> = _uiStateError
+    val uiStateError: LiveData<Event<CwaWebException>> = _uiStateError
 
     val submissionState: LiveData<Event<ApiRequestState>> = _submissionState
-    val submissionError: LiveData<Event<Exception>> = _submissionError
+    val submissionError: LiveData<Event<CwaWebException>> = _submissionError
 
     val deviceRegistered get() = LocalData.registrationToken() != null
 
@@ -89,16 +92,18 @@ class SubmissionViewModel : ViewModel() {
     private fun executeRequestWithState(
         apiRequest: suspend () -> Unit,
         state: MutableLiveData<ApiRequestState>,
-        exceptionLiveData: MutableLiveData<Event<Exception>>? = null
+        exceptionLiveData: MutableLiveData<Event<CwaWebException>>? = null
     ) {
         state.value = ApiRequestState.STARTED
         viewModelScope.launch {
             try {
                 apiRequest()
                 state.value = ApiRequestState.SUCCESS
-            } catch (err: Exception) {
+            } catch (err: CwaWebException) {
                 exceptionLiveData?.value = Event(err)
                 state.value = ApiRequestState.FAILED
+            } catch (err: Exception) {
+                err.report(ExceptionCategory.INTERNAL)
             }
         }
     }
@@ -106,16 +111,18 @@ class SubmissionViewModel : ViewModel() {
     private fun executeRequestWithStateForEvent(
         apiRequest: suspend () -> Unit,
         state: MutableLiveData<Event<ApiRequestState>>,
-        exceptionLiveData: MutableLiveData<Event<Exception>>? = null
+        exceptionLiveData: MutableLiveData<Event<CwaWebException>>? = null
     ) {
         state.value = Event(ApiRequestState.STARTED)
         viewModelScope.launch {
             try {
                 apiRequest()
                 state.value = Event(ApiRequestState.SUCCESS)
-            } catch (err: Exception) {
+            } catch (err: CwaWebException) {
                 exceptionLiveData?.value = Event(err)
                 state.value = Event(ApiRequestState.FAILED)
+            } catch (err: Exception) {
+                err.report(ExceptionCategory.INTERNAL)
             }
         }
     }
-- 
GitLab