diff --git a/app/src/androidTest/kotlin/ee/ria/DigiDoc/IdCardDataCreator.kt b/app/src/androidTest/kotlin/ee/ria/DigiDoc/IdCardDataCreator.kt
index 9f9c784c9..5cb77e581 100644
--- a/app/src/androidTest/kotlin/ee/ria/DigiDoc/IdCardDataCreator.kt
+++ b/app/src/androidTest/kotlin/ee/ria/DigiDoc/IdCardDataCreator.kt
@@ -39,6 +39,7 @@ class IdCardDataCreator {
pin1RetryCount: Int = 3,
pin2RetryCount: Int = 3,
pukRetryCount: Int = 3,
+ pin1CodeChanged: Boolean = true,
pin2CodeChanged: Boolean = true,
): IdCardData =
IdCardData(
@@ -49,6 +50,7 @@ class IdCardDataCreator {
pin1RetryCount = pin1RetryCount,
pin2RetryCount = pin2RetryCount,
pukRetryCount = pukRetryCount,
+ pin1CodeChanged = pin1CodeChanged,
pin2CodeChanged = pin2CodeChanged,
)
diff --git a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/IdCardViewModelTest.kt b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/IdCardViewModelTest.kt
index 377195801..b8f4b26b5 100644
--- a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/IdCardViewModelTest.kt
+++ b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/IdCardViewModelTest.kt
@@ -206,6 +206,7 @@ class IdCardViewModelTest {
3,
3,
true,
+ true,
)
`when`(idCardService.data(anyOrNull())).thenReturn(idCardData)
@@ -280,6 +281,7 @@ class IdCardViewModelTest {
3,
3,
true,
+ true,
)
`when`(idCardService.data(anyOrNull())).thenReturn(idCardData)
@@ -342,6 +344,7 @@ class IdCardViewModelTest {
2,
3,
true,
+ true,
)
`when`(idCardService.data(anyOrNull())).thenReturn(idCardData)
@@ -409,6 +412,7 @@ class IdCardViewModelTest {
1,
3,
true,
+ true,
)
`when`(idCardService.data(anyOrNull())).thenReturn(idCardData)
@@ -476,6 +480,7 @@ class IdCardViewModelTest {
0,
3,
true,
+ true,
)
`when`(idCardService.data(anyOrNull())).thenReturn(idCardData)
@@ -541,6 +546,7 @@ class IdCardViewModelTest {
3,
0,
3,
+ true,
false,
)
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/DecryptScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/DecryptScreen.kt
index 2f12b3365..8959def29 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/DecryptScreen.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/DecryptScreen.kt
@@ -281,6 +281,7 @@ fun DecryptScreen(
isValidToDecrypt = { isValid ->
isValidToDecrypt = isValid
},
+ onCourierCardDetected = {},
decryptAction = { action ->
decryptAction = {
isDecrypting = true
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/SignatureInputScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/SignatureInputScreen.kt
index 2173c468a..aabda53ec 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/SignatureInputScreen.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/SignatureInputScreen.kt
@@ -368,6 +368,10 @@ fun SignatureInputScreen(
isValidToSign = { isValid ->
isValidToSign = isValid
},
+ onCourierCardDetected = {},
+ onCourierCardDialogDismissed = {
+ navController.navigateUp()
+ },
signAction = { action ->
signAction = {
isSigning = true
@@ -408,6 +412,9 @@ fun SignatureInputScreen(
isValidToSign = { isValid ->
isValidToSign = isValid
},
+ onCourierCardDialogDismissed = {
+ navController.navigateUp()
+ },
signAction = { action ->
signAction = action
},
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/MyEidScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/MyEidScreen.kt
index 5f0510438..091691103 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/MyEidScreen.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/MyEidScreen.kt
@@ -117,9 +117,10 @@ fun MyEidScreen(
val isPin2Blocked = idCardData?.pin2RetryCount == 0
val isPukBlocked = idCardData?.pukRetryCount == 0
val isPin2Activated = idCardData?.pin2CodeChanged == true
+ val isCourierCard = idCardData?.personalData != null && idCardData?.pin1CodeChanged == false
- val alphaForPin1BlockedState = getAlphaForBlockedState(isPin1Blocked && isPukBlocked)
- val alphaForPin2BlockedState = getAlphaForBlockedState(isPin2Blocked && isPukBlocked)
+ val alphaForPin1BlockedState = getAlphaForBlockedState((isPin1Blocked && isPukBlocked) || isCourierCard)
+ val alphaForPin2BlockedState = getAlphaForBlockedState((isPin2Blocked && isPukBlocked) || isCourierCard)
val alphaForPukBlockedState = getAlphaForBlockedState(isPukBlocked)
val selectedMyEidTabIndex = rememberSaveable { mutableIntStateOf(0) }
@@ -378,6 +379,7 @@ fun MyEidScreen(
),
isPinBlocked = isPin1Blocked,
isPukBlocked = isPukBlocked,
+ isNotActivated = isCourierCard,
forgotPinText =
if (isPin1Blocked) {
stringResource(
@@ -434,6 +436,12 @@ fun MyEidScreen(
style = MaterialTheme.typography.bodySmall,
)
}
+ if (isCourierCard) {
+ CourierCardWarningText(
+ modifier = modifier,
+ testTag = "myEidCourierCardPin1DescriptionText",
+ )
+ }
}
}
item {
@@ -459,6 +467,7 @@ fun MyEidScreen(
),
isPinBlocked = isPin2Blocked,
isPukBlocked = isPukBlocked,
+ isNotActivated = isCourierCard,
forgotPinText =
if (isPin2Blocked) {
stringResource(
@@ -484,7 +493,12 @@ fun MyEidScreen(
},
)
- if (!isPin2Activated) {
+ if (isCourierCard) {
+ CourierCardWarningText(
+ modifier = modifier,
+ testTag = "myEidCourierCardPin2DescriptionText",
+ )
+ } else if (!isPin2Activated) {
Text(
modifier =
modifier
@@ -670,4 +684,36 @@ fun MyEidScreen(
)
}
+@Composable
+private fun CourierCardWarningText(
+ modifier: Modifier,
+ testTag: String,
+) {
+ val message = stringResource(R.string.id_card_courier_warning_message)
+ val linkText = stringResource(R.string.id_card_courier_activate_button)
+ val linkUrl = stringResource(R.string.id_card_courier_activate_url)
+ val linkWord = stringResource(R.string.link)
+ HrefDynamicText(
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .focusable(true)
+ .testTag(testTag)
+ .semantics {
+ contentDescription = "$message $linkText, $linkWord, $linkUrl"
+ },
+ text1 = message,
+ text2 = null,
+ linkText = linkText,
+ linkUrl = linkUrl,
+ newLineBeforeLink = true,
+ textStyle =
+ TextStyle(
+ color = MaterialTheme.colorScheme.error,
+ fontSize = MaterialTheme.typography.bodySmall.fontSize,
+ textAlign = TextAlign.Start,
+ ),
+ )
+}
+
fun getAlphaForBlockedState(isBlocked: Boolean) = if (!isBlocked) 1f else 0.7f
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/pinandcertificate/MyEidPinAndCertificateView.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/pinandcertificate/MyEidPinAndCertificateView.kt
index 6fa75cf17..e41097802 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/pinandcertificate/MyEidPinAndCertificateView.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/pinandcertificate/MyEidPinAndCertificateView.kt
@@ -78,6 +78,7 @@ fun MyEidPinAndCertificateView(
linkUrl: String = "",
isPinBlocked: Boolean = false,
isPukBlocked: Boolean = false,
+ isNotActivated: Boolean = false,
showForgotPin: Boolean = true,
forgotPinText: String = "",
onForgotPinClick: (() -> Unit)? = null,
@@ -130,6 +131,7 @@ fun MyEidPinAndCertificateView(
modifier =
modifier
.weight(1f)
+ .padding(vertical = XSPadding)
.focusable()
.semantics(mergeDescendants = true) {
this.contentDescription = "$title. $subtitle".lowercase()
@@ -179,7 +181,7 @@ fun MyEidPinAndCertificateView(
verticalAlignment = Alignment.CenterVertically,
) {
OutlinedButton(
- enabled = !isPukBlocked,
+ enabled = !isPukBlocked && !isNotActivated,
onClick = onForgotPinClick,
modifier =
modifier
@@ -209,7 +211,7 @@ fun MyEidPinAndCertificateView(
}
Button(
- enabled = !isPinBlocked,
+ enabled = !isPinBlocked && !isNotActivated,
onClick = onChangePinClick ?: {},
modifier =
modifier
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/dialog/CourierCardActivationDialog.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/dialog/CourierCardActivationDialog.kt
new file mode 100644
index 000000000..5c15006e8
--- /dev/null
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/dialog/CourierCardActivationDialog.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2017 - 2026 Riigi Infosüsteemi Amet
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+@file:Suppress("PackageName", "FunctionName")
+
+package ee.ria.DigiDoc.ui.component.shared.dialog
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.BasicAlertDialog
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTagsAsResourceId
+import androidx.compose.ui.text.style.TextAlign
+import ee.ria.DigiDoc.R
+import ee.ria.DigiDoc.ui.component.shared.CancelAndOkButtonRow
+import ee.ria.DigiDoc.ui.component.shared.InvisibleElement
+import ee.ria.DigiDoc.ui.theme.Dimensions.SPadding
+import ee.ria.DigiDoc.ui.theme.buttonRoundCornerShape
+import kotlinx.coroutines.delay
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
+@Composable
+fun CourierCardActivationDialog(
+ modifier: Modifier = Modifier,
+ onDismiss: () -> Unit,
+) {
+ val focusRequester = remember { FocusRequester() }
+ Box(modifier = modifier.fillMaxSize()) {
+ BasicAlertDialog(
+ modifier =
+ Modifier
+ .clip(buttonRoundCornerShape)
+ .background(MaterialTheme.colorScheme.surface),
+ onDismissRequest = onDismiss,
+ ) {
+ LaunchedEffect(Unit) {
+ delay(100)
+ focusRequester.requestFocus()
+ }
+ Surface(
+ modifier =
+ Modifier
+ .padding(SPadding)
+ .wrapContentHeight()
+ .wrapContentWidth()
+ .verticalScroll(rememberScrollState()),
+ ) {
+ Column(
+ modifier =
+ Modifier
+ .semantics { testTagsAsResourceId = true }
+ .testTag("courierCardActivationDialogContainer"),
+ ) {
+ Text(
+ modifier =
+ Modifier
+ .padding(SPadding)
+ .fillMaxWidth()
+ .focusRequester(focusRequester)
+ .focusable(),
+ text = stringResource(R.string.id_card_courier_must_activate_to_sign),
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.onBackground,
+ textAlign = TextAlign.Start,
+ )
+ CancelAndOkButtonRow(
+ okButtonTestTag = "courierCardDialogOkButton",
+ cancelButtonTestTag = "courierCardDialogCancelButton",
+ cancelButtonClick = {},
+ okButtonClick = onDismiss,
+ cancelButtonTitle = R.string.cancel_button,
+ okButtonTitle = R.string.ok_button,
+ cancelButtonContentDescription = stringResource(R.string.cancel_button).lowercase(),
+ okButtonContentDescription = stringResource(R.string.ok_button).lowercase(),
+ showCancelButton = false,
+ )
+ }
+ }
+ }
+ InvisibleElement(modifier = Modifier)
+ }
+}
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/IdCardView.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/IdCardView.kt
index 8282cbf71..582c95480 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/IdCardView.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/IdCardView.kt
@@ -97,6 +97,7 @@ import ee.ria.DigiDoc.ui.component.shared.HrefMessageDialog
import ee.ria.DigiDoc.ui.component.shared.InvisibleElement
import ee.ria.DigiDoc.ui.component.shared.RoleDataView
import ee.ria.DigiDoc.ui.component.shared.SecurePinTextField
+import ee.ria.DigiDoc.ui.component.shared.dialog.CourierCardActivationDialog
import ee.ria.DigiDoc.ui.theme.Dimensions.LPadding
import ee.ria.DigiDoc.ui.theme.Dimensions.MSPadding
import ee.ria.DigiDoc.ui.theme.Dimensions.SPadding
@@ -140,6 +141,8 @@ fun IdCardView(
isValidToSign: (Boolean) -> Unit = {},
isAuthenticated: (Boolean, IdCardData) -> Unit,
isValidToDecrypt: (Boolean) -> Unit = {},
+ onCourierCardDetected: (Boolean) -> Unit = {},
+ onCourierCardDialogDismissed: () -> Unit = {},
signAction: (() -> Unit) -> Unit = {},
decryptAction: (() -> Unit) -> Unit = {},
cancelAction: (() -> Unit) -> Unit = {},
@@ -175,10 +178,16 @@ fun IdCardView(
""
}
- val idCardStatusReadyToSign = idCardData?.personalData != null && !isSigning && !isAuthenticating && !isDecrypting
+ val isCourierCard =
+ idCardStatus == SmartCardReaderStatus.CARD_DETECTED &&
+ idCardData?.personalData != null &&
+ idCardData?.pin1CodeChanged == false
+ val idCardStatusReadyToSign =
+ idCardData?.personalData != null && !isSigning && !isAuthenticating && !isDecrypting && !isCourierCard
val shouldHandleError by idCardViewModel.shouldHandleError.collectAsState()
val showErrorDialog = rememberSaveable { mutableStateOf(false) }
+ val showCourierCardDialog = rememberSaveable { mutableStateOf(false) }
var isDataLoadingStarted by rememberSaveable { mutableStateOf(false) }
var showLoadingIndicator by rememberSaveable { mutableStateOf(false) }
@@ -237,7 +246,6 @@ fun IdCardView(
val statusMessageFocusRequester = remember { FocusRequester() }
val readyToSignFocusRequester = remember { FocusRequester() }
val pinCodeFocusRequester = remember { FocusRequester() }
-
var isValid by rememberSaveable { mutableStateOf(false) }
val clearButtonText = stringResource(R.string.clear_text)
@@ -423,6 +431,20 @@ fun IdCardView(
}
}
+ LaunchedEffect(isCourierCard) {
+ if (isCourierCard) {
+ isValidToSign(false)
+ isValidToDecrypt(false)
+ onCourierCardDetected(true)
+ if (identityAction == IdentityAction.SIGN) {
+ showCourierCardDialog.value = true
+ }
+ } else {
+ showCourierCardDialog.value = false
+ onCourierCardDetected(false)
+ }
+ }
+
LaunchedEffect(Unit, idCardData, isValid, isSigning, isDecrypting, isAuthenticating, idCardStatusMessage) {
if (idCardData?.personalData == null ||
(isValid && isSigning) ||
@@ -531,6 +553,13 @@ fun IdCardView(
}
}
+ if (showCourierCardDialog.value) {
+ CourierCardActivationDialog(onDismiss = {
+ showCourierCardDialog.value = false
+ onCourierCardDialogDismissed()
+ })
+ }
+
Column(
modifier =
modifier
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/NFCView.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/NFCView.kt
index 70f8f0142..ed79d5eb5 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/NFCView.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/NFCView.kt
@@ -106,6 +106,7 @@ import ee.ria.DigiDoc.ui.component.shared.HrefMessageDialog
import ee.ria.DigiDoc.ui.component.shared.InvisibleElement
import ee.ria.DigiDoc.ui.component.shared.RoleDataView
import ee.ria.DigiDoc.ui.component.shared.SecurePinTextField
+import ee.ria.DigiDoc.ui.component.shared.dialog.CourierCardActivationDialog
import ee.ria.DigiDoc.ui.component.support.textFieldValueSaver
import ee.ria.DigiDoc.ui.theme.Dimensions.MSPadding
import ee.ria.DigiDoc.ui.theme.Dimensions.SPadding
@@ -149,6 +150,7 @@ fun NFCView(
isSupported: (Boolean) -> Unit = {},
isValidToSign: (Boolean) -> Unit = {},
isValidToDecrypt: (Boolean) -> Unit = {},
+ onCourierCardDialogDismissed: () -> Unit = {},
showPinField: Boolean = true,
isValidToAuthenticate: (Boolean) -> Unit,
signAction: (() -> Unit) -> Unit = {},
@@ -185,6 +187,7 @@ fun NFCView(
}
var errorText by remember { mutableStateOf("") }
val showErrorDialog = rememberSaveable { mutableStateOf(false) }
+ val showCourierCardDialog = rememberSaveable { mutableStateOf(false) }
val focusManager = LocalFocusManager.current
val saveFormParams = {
if (shouldRememberMe) {
@@ -199,6 +202,8 @@ fun NFCView(
val canNumberFocusRequester = remember { FocusRequester() }
val pinNumberFocusRequester = remember { FocusRequester() }
+
+ val isCourierCard = personalData?.personalData != null && personalData?.pin1CodeChanged == false
val canNumberWithInvisibleSpaces = TextFieldValue(addInvisibleElement(canNumber.text))
val pinCode = remember { mutableStateOf(byteArrayOf()) }
@@ -235,6 +240,10 @@ fun NFCView(
}
}
+ LaunchedEffect(Unit) {
+ nfcViewModel.resetIdCardUserData()
+ }
+
LaunchedEffect(nfcViewModel.shouldResetPIN) {
nfcViewModel.shouldResetPIN.asFlow().collect { bool ->
bool.let {
@@ -320,10 +329,14 @@ fun NFCView(
.asFlow()
.filterNotNull()
.filterNot { it == 0 }
- .collect {
+ .collect { error ->
withContext(Main) {
nfcViewModel.resetErrorState()
- showErrorDialog.value = true
+ if (error == R.string.id_card_courier_must_activate_to_sign) {
+ showCourierCardDialog.value = true
+ } else {
+ showErrorDialog.value = true
+ }
}
}
}
@@ -352,11 +365,30 @@ fun NFCView(
}
}
+ LaunchedEffect(isCourierCard) {
+ if (isCourierCard) {
+ isValidToSign(false)
+ isValidToDecrypt(false)
+ if (identityAction == IdentityAction.SIGN) {
+ showCourierCardDialog.value = true
+ }
+ } else {
+ showCourierCardDialog.value = false
+ }
+ }
+
if (errorText.isNotEmpty()) {
showMessage(errorText)
errorText = ""
}
+ if (showCourierCardDialog.value) {
+ CourierCardActivationDialog(onDismiss = {
+ showCourierCardDialog.value = false
+ onCourierCardDialogDismissed()
+ })
+ }
+
if (showErrorDialog.value) {
var text1Arg: Int? = null
val text2 = null
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SigningNavigation.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SigningNavigation.kt
index b262ff2df..df75977e8 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SigningNavigation.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SigningNavigation.kt
@@ -26,7 +26,6 @@ import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
-import android.util.Log
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModel.kt b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModel.kt
index ca4f9f520..3be39ec97 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModel.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModel.kt
@@ -57,7 +57,6 @@ import ee.ria.DigiDoc.utils.pin.PinCodeUtil.isPINLengthValid
import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.debugLog
import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.errorLog
import ee.ria.libdigidocpp.ExternalSigner
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
@@ -229,11 +228,14 @@ class NFCViewModel
nfcSmartCardReaderManager.startDiscovery(activity) { nfcReader, exc ->
if ((nfcReader != null) && (exc == null)) {
try {
- CoroutineScope(Main).launch {
- _message.postValue(R.string.signature_update_nfc_detected)
- }
+ _message.postValue(R.string.signature_update_nfc_detected)
val card = TokenWithPace.create(nfcReader)
card.tunnel(canNumber)
+ val pin1ChangedFlagValue = card.pinChangedFlag(CodeType.PIN1)
+ if (pin1ChangedFlagValue == 1) {
+ _dialogError.postValue(R.string.id_card_courier_must_activate_to_sign)
+ return@startDiscovery
+ }
val signerCert = card.certificate(CertificateType.SIGNING)
debugLog(
logTag,
@@ -260,11 +262,9 @@ class NFCViewModel
signatureArray,
)
- CoroutineScope(Main).launch {
- _shouldResetPIN.postValue(true)
- _signStatus.postValue(true)
- _signedContainer.postValue(container)
- }
+ _shouldResetPIN.postValue(true)
+ _signStatus.postValue(true)
+ _signedContainer.postValue(container)
} catch (ex: SmartCardReaderException) {
_signStatus.postValue(false)
@@ -388,9 +388,7 @@ class NFCViewModel
nfcSmartCardReaderManager.startDiscovery(activity) { nfcReader, exc ->
if ((nfcReader != null) && (exc == null)) {
try {
- CoroutineScope(Main).launch {
- _message.postValue(R.string.signature_update_nfc_detected)
- }
+ _message.postValue(R.string.signature_update_nfc_detected)
val card = TokenWithPace.create(nfcReader)
card.tunnel(canNumber)
@@ -414,11 +412,9 @@ class NFCViewModel
if (pin1Code.isNotEmpty()) {
Arrays.fill(pin1Code, 0.toByte())
}
- CoroutineScope(Main).launch {
- _shouldResetPIN.postValue(true)
- _decryptStatus.postValue(true)
- _cryptoContainer.postValue(decryptedContainer)
- }
+ _shouldResetPIN.postValue(true)
+ _decryptStatus.postValue(true)
+ _cryptoContainer.postValue(decryptedContainer)
} catch (ex: SmartCardReaderException) {
_decryptStatus.postValue(false)
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index bb14e4803..855d1aa18 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -647,6 +647,10 @@
%1$s on blokeeritud, kuna %1$s-koodi on sisestatud 3 korda valesti.
Selle ID-kaardiga allkirjastamine ei ole veel võimalik. Allkirjastamiseks tuleb %1$s-koodi muuta.
+ ID-kaardiga isikutuvastamine ja allkirjastamine ei ole veel võimalik. ID-kaardi kasutamiseks tuleb see aktiveerida Politsei- ja Piirivalveameti iseteeninduses.
+ Aktiveeri ID-kaart
+ https://www.politsei.ee/et/iseteenindus/
+ Allkirjastamiseks tuleb ID-kaart aktiveerida.
%1$s on blokeeritud, kuna %1$s-koodi on sisestatud 3 korda valesti. Tühista blokeering, et %1$s taas kasutada.
PUK on blokeeritud, kuna PUK-koodi on sisestatud 3 korda valesti. PUK-koodi ei saa ise lahti blokeerida.\n\nKuigi PUK-kood on blokeeritud, saab kõiki eID võimalusi kasutada, välja arvatud PUK-koodi vajavaid.\n\nUue PUK-koodi saamiseks külasta klienditeeninduspunkti, kust saad uue koodiümbriku uute koodidega.
https://www.id.ee/artikkel/id-kaardi-pin-ja-puk-koodide-muutmine/
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bf64e3f8b..b6bf54abe 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -647,6 +647,10 @@
%1$s is blocked, because %1$s has been inserted 3 times incorrectly.
Signing with the ID-card isn\'t possible yet. %1$s code must be changed in order to sign.
+ Authentication and signing with the ID-card isn\'t possible yet. ID-card must be activated in the Police and Border Guard Board\'s self-service portal in order to use it.
+ Activate ID-card
+ https://www.politsei.ee/en/self-service-portal/
+ The ID-card must be activated in order to sign.
%1$s has been blocked because %1$s code has been entered incorrectly 3 times. Unblock to reuse %1$s.
PUK has been blocked because PUK code has been entered incorrectly 3 times. You can not unblock the PUK code yourself.\n\nAs long as the PUK code is blocked, all eID options can be used, except transactions that need PUK code.\n\nPlease visit the service center to obtain new codes.
https://www.id.ee/en/article/changing-id-card-pin-codes-and-puk-code/
diff --git a/id-card-lib/id-lib/id-card-lib-debug.aar b/id-card-lib/id-lib/id-card-lib-debug.aar
index c3df9294a..855b490dd 100644
Binary files a/id-card-lib/id-lib/id-card-lib-debug.aar and b/id-card-lib/id-lib/id-card-lib-debug.aar differ
diff --git a/id-card-lib/id-lib/id-card-lib-release.aar b/id-card-lib/id-lib/id-card-lib-release.aar
index 9ae349e84..2183b893d 100644
Binary files a/id-card-lib/id-lib/id-card-lib-release.aar and b/id-card-lib/id-lib/id-card-lib-release.aar differ
diff --git a/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/model/IdCardData.kt b/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/model/IdCardData.kt
index 5c86a7bb3..1dcd0f2fd 100644
--- a/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/model/IdCardData.kt
+++ b/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/model/IdCardData.kt
@@ -33,5 +33,6 @@ data class IdCardData(
val pin1RetryCount: Int,
val pin2RetryCount: Int,
val pukRetryCount: Int,
+ val pin1CodeChanged: Boolean,
val pin2CodeChanged: Boolean,
)
diff --git a/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/service/IdCardServiceImpl.kt b/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/service/IdCardServiceImpl.kt
index 734c785fd..14243a45a 100644
--- a/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/service/IdCardServiceImpl.kt
+++ b/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/service/IdCardServiceImpl.kt
@@ -73,7 +73,8 @@ class IdCardServiceImpl
val pin1RetryCounter = token.codeRetryCounter(CodeType.PIN1)
val pin2RetryCounter = token.codeRetryCounter(CodeType.PIN2)
val pukRetryCounter = token.codeRetryCounter(CodeType.PUK)
- val pin2CodeChanged = token.pinChangedFlag()
+ val pin1CodeChanged = token.pinChangedFlag(CodeType.PIN1)
+ val pin2CodeChanged = token.pinChangedFlag(CodeType.PIN2)
val authCertificate = ExtendedCertificate.create(authenticationCertificateData, certificateService)
val signCertificate = ExtendedCertificate.create(signingCertificateData, certificateService)
@@ -86,6 +87,7 @@ class IdCardServiceImpl
pin1RetryCount = pin1RetryCounter,
pin2RetryCount = pin2RetryCounter,
pukRetryCount = pukRetryCounter,
+ pin1CodeChanged = pin1CodeChanged == 1,
pin2CodeChanged = pin2CodeChanged == 1,
)
}
diff --git a/id-card-lib/src/test/kotlin/ee/ria/DigiDoc/domain/model/IdCardDataTest.kt b/id-card-lib/src/test/kotlin/ee/ria/DigiDoc/domain/model/IdCardDataTest.kt
new file mode 100644
index 000000000..e33b9719e
--- /dev/null
+++ b/id-card-lib/src/test/kotlin/ee/ria/DigiDoc/domain/model/IdCardDataTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 - 2026 Riigi Infosüsteemi Amet
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+@file:Suppress("PackageName")
+
+package ee.ria.DigiDoc.domain.model
+
+import ee.ria.DigiDoc.common.model.EIDType
+import ee.ria.DigiDoc.common.model.ExtendedCertificate
+import ee.ria.DigiDoc.idcard.PersonalData
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
+class IdCardDataTest {
+ private val personalData: PersonalData = mock(PersonalData::class.java)
+ private val authCertificate: ExtendedCertificate = mock(ExtendedCertificate::class.java)
+ private val signCertificate: ExtendedCertificate = mock(ExtendedCertificate::class.java)
+
+ init {
+ `when`(authCertificate.type).thenReturn(EIDType.ID_CARD)
+ `when`(signCertificate.type).thenReturn(EIDType.ID_CARD)
+ }
+
+ private fun buildIdCardData(
+ pin1CodeChanged: Boolean,
+ pin2CodeChanged: Boolean,
+ ) = IdCardData(
+ type = EIDType.ID_CARD,
+ personalData = personalData,
+ authCertificate = authCertificate,
+ signCertificate = signCertificate,
+ pin1RetryCount = 3,
+ pin2RetryCount = 3,
+ pukRetryCount = 3,
+ pin1CodeChanged = pin1CodeChanged,
+ pin2CodeChanged = pin2CodeChanged,
+ )
+
+ @Test
+ fun idCardData_pin1CodeChanged_whenFlagIsOne_isTrue() {
+ val data = buildIdCardData(pin1CodeChanged = true, pin2CodeChanged = true)
+ assertTrue(data.pin1CodeChanged)
+ }
+
+ @Test
+ fun idCardData_pin1CodeChanged_whenFlagIsZero_isFalse() {
+ val data = buildIdCardData(pin1CodeChanged = false, pin2CodeChanged = false)
+ assertFalse(data.pin1CodeChanged)
+ }
+
+ @Test
+ fun idCardData_pin2CodeChanged_whenFlagIsOne_isTrue() {
+ val data = buildIdCardData(pin1CodeChanged = true, pin2CodeChanged = true)
+ assertTrue(data.pin2CodeChanged)
+ }
+
+ @Test
+ fun idCardData_pin2CodeChanged_whenFlagIsZero_isFalse() {
+ val data = buildIdCardData(pin1CodeChanged = true, pin2CodeChanged = false)
+ assertFalse(data.pin2CodeChanged)
+ }
+
+ @Test
+ fun idCardData_courierCard_pin1NotChanged_isCourierCard() {
+ val data = buildIdCardData(pin1CodeChanged = false, pin2CodeChanged = false)
+ val isCourierCard = !data.pin1CodeChanged
+ assertTrue(isCourierCard)
+ }
+
+ @Test
+ fun idCardData_activatedCard_pin1Changed_isNotCourierCard() {
+ val data = buildIdCardData(pin1CodeChanged = true, pin2CodeChanged = true)
+ val isCourierCard = !data.pin1CodeChanged
+ assertFalse(isCourierCard)
+ }
+
+ @Test
+ fun idCardData_regularThalesCard_pin1ChangedPin2Not_isNotCourierCard() {
+ // Regular Thales card: PIN1 has been changed, only PIN2 is unused
+ val data = buildIdCardData(pin1CodeChanged = true, pin2CodeChanged = false)
+ val isCourierCard = !data.pin1CodeChanged
+ assertFalse(isCourierCard)
+ }
+}