Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge 5.0 to dev #335

Merged
merged 5 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions tangem-sdk-android-demo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ dependencies {
implementation project(':tangem-sdk-core')
implementation project(':tangem-sdk-android')

testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

implementation "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlin"
implementation 'androidx.core:core-ktx:1.3.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,12 @@ class SdkTaskSpinnerFragment : BaseFragment() {

class EmptyViewDelegate : SessionViewDelegate {
override val resetCodesViewDelegate: ResetCodesViewDelegate = EmptyResetCodesViewDelegate()
override fun onSessionStarted(cardId: String?, message: ViewDelegateMessage?, enableHowTo: Boolean) {}
override fun onSessionStarted(
cardId: String?,
message: ViewDelegateMessage?,
enableHowTo: Boolean,
iconScanRes: Int?,
) {}
override fun onSecurityDelay(ms: Int, totalDurationSeconds: Int) {}
override fun onDelay(total: Int, current: Int, step: Int) {}
override fun onTagLost() {}
Expand Down
6 changes: 3 additions & 3 deletions tangem-sdk-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ dependencies {
implementation 'at.favre.lib:armadillo:1.0.0'

// testing
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,14 @@ class DefaultSessionViewDelegate(
private var readingDialog: NfcSessionDialog? = null
private var stoppedBySession: Boolean = false

override fun onSessionStarted(cardId: String?, message: ViewDelegateMessage?, enableHowTo: Boolean) {
override fun onSessionStarted(
cardId: String?,
message: ViewDelegateMessage?,
enableHowTo: Boolean,
iconScanRes: Int?,
) {
Log.view { "session started" }
createAndShowState(SessionViewDelegateState.Ready(formatCardId(cardId)), enableHowTo, message)
createAndShowState(SessionViewDelegateState.Ready(formatCardId(cardId)), enableHowTo, message, iconScanRes)
}

override fun onSessionStopped(message: Message?) {
Expand Down Expand Up @@ -146,10 +151,11 @@ class DefaultSessionViewDelegate(
state: SessionViewDelegateState,
enableHowTo: Boolean,
message: ViewDelegateMessage? = null,
iconScanRes: Int? = null,
) {
postUI {
if (readingDialog == null) {
createReadingDialog(activity)
createReadingDialog(activity, iconScanRes)
} else {
readingDialog?.dismissInternal()
}
Expand All @@ -160,12 +166,13 @@ class DefaultSessionViewDelegate(
}
}

private fun createReadingDialog(activity: Activity) {
private fun createReadingDialog(activity: Activity, iconScanRes: Int? = null) {
val nfcLocationProvider = NfcAntennaLocationProvider(Build.DEVICE)
readingDialog = NfcSessionDialog(
context = activity.sdkThemeContext(),
nfcManager = nfcManager,
nfcLocationProvider = nfcLocationProvider,
iconScanRes = iconScanRes,
).apply {
setOwnerActivity(activity)
dismissWithAnimation = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,18 @@ internal class AndroidKeystoreManager(
return try {
RSACipherOperations.initUnwrapKeyCipher(masterPrivateKey)
} catch (e: UserNotAuthenticatedException) {
Log.warning { "$TAG - Unable to initialize the cipher because the user is not authenticated" }

authenticationManager.authenticate()

initUnwrapCipherAfterAuthentication()
authenticateAndInitUnwrapCipher()
} catch (e: InvalidKeyException) {
handleInvalidKeyException(e)
}
}

private fun initUnwrapCipherAfterAuthentication(): Cipher {
Log.debug { "$TAG - Reinitializing the unwrap cipher" }
private suspend fun authenticateAndInitUnwrapCipher(): Cipher {
Log.warning { "$TAG - Unable to initialize the cipher because the user is not authenticated" }

return try {
authenticationManager.authenticate()

RSACipherOperations.initUnwrapKeyCipher(masterPrivateKey)
} catch (e: InvalidKeyException) {
handleInvalidKeyException(e)
Expand Down Expand Up @@ -157,7 +157,6 @@ internal class AndroidKeystoreManager(
.let { builder ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
builder
.setUnlockedDeviceRequired(true)
.setUserAuthenticationParameters(
MASTER_KEY_TIMEOUT_SECONDS,
KeyProperties.AUTH_BIOMETRIC_STRONG,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class NfcSessionDialog(
context: Context,
private val nfcManager: NfcManager,
private val nfcLocationProvider: NfcLocationProvider,
private val iconScanRes: Int? = null,
) : BaseSdkDialog(context) {

private lateinit var taskContainer: ViewGroup
Expand Down Expand Up @@ -67,6 +68,7 @@ class NfcSessionDialog(

headerWidget = HeaderWidget(view.findViewById(R.id.llHeader))
touchCardWidget = TouchCardWidget(view.findViewById(R.id.flImageContainer), nfcLocation)
touchCardWidget.setIconScanRes(iconScanRes)
progressStateWidget = ProgressbarStateWidget(view.findViewById(R.id.clProgress))
pinCodeRequestWidget = PinCodeRequestWidget(view.findViewById(R.id.csPinCode))
pinCodeSetChangeWidget = PinCodeModificationWidget(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@ class TouchCardWidget(
customBitmapHolder?.bitmap?.recycle()
}

fun setIconScanRes(iconScanRes: Int?) {
if (iconScanRes != null) {
ivHandCardHorizontal.setImageResource(iconScanRes)
ivHandCardVertical.setImageResource(iconScanRes)
} else {
ivHandCardHorizontal.setImageResource(R.drawable.hand_full_card_horizontal)
ivHandCardVertical.setImageResource(R.drawable.hand_full_card_vertical)
}
}

private data class BitmapImageHolder(
val bitmapArray: ByteArray,
val bitmap: Bitmap = BitmapFactory.decodeByteArray(bitmapArray, 0, bitmapArray.size, BitmapFactory.Options()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ interface SessionViewDelegate {
/**
* It is called when user is expected to scan a Tangem Card with an Android device.
*/
fun onSessionStarted(cardId: String?, message: ViewDelegateMessage? = null, enableHowTo: Boolean)
fun onSessionStarted(
cardId: String?,
message: ViewDelegateMessage? = null,
enableHowTo: Boolean,
iconScanRes: Int? = null,
)

/**
* It is called when security delay is triggered by the card.
Expand Down
13 changes: 10 additions & 3 deletions tangem-sdk-core/src/main/java/com/tangem/TangemSdk.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,7 @@ class TangemSdk(
cardId: String? = null,
initialMessage: Message? = null,
accessCode: String? = null,
iconScanRes: Int? = null,
callback: CompletionCallback<T>,
) {
if (checkSession()) {
Expand All @@ -1030,7 +1031,13 @@ class TangemSdk(

configure()
cardSession = makeSession(cardId, initialMessage, accessCode)
Thread().run { cardSession?.startWithRunnable(runnable, callback) }
Thread().run {
cardSession?.startWithRunnable(
iconScanRes = iconScanRes,
runnable = runnable,
callback = callback,
)
}
}

/**
Expand Down Expand Up @@ -1103,15 +1110,15 @@ class TangemSdk(
val jsonrpcLinker = linkersList[0]
cardSession = makeSession(cardId, message, accessCode)
Thread().run {
cardSession?.startWithRunnable(jsonrpcLinker.runnable!!) {
cardSession?.startWithRunnable(runnable = jsonrpcLinker.runnable!!) {
jsonrpcLinker.linkResult(it)
callback(jsonrpcLinker.response.toJson())
}
}
} else {
val task = RunnablesTask(linkersList)
cardSession = makeSession(cardId, message, accessCode)
cardSession!!.startWithRunnable(task) { result ->
cardSession!!.startWithRunnable(runnable = task) { result ->
when (result) {
is CompletionResult.Success -> callback(converter.toJson(result.data.responses))
is CompletionResult.Failure -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,15 @@ class CardSession(
/**
* This method starts a card session, performs preflight [ReadCommand],
* invokes [CardSessionRunnable.run] and closes the session.
* @param iconScanRes iconResource to replace on Scan Bottom Sheet
* @param runnable [CardSessionRunnable] that will be performed in the session.
* @param callback will be triggered with a [CompletionResult] of a session.
*/
fun <T : CardSessionRunnable<R>, R> startWithRunnable(runnable: T, callback: CompletionCallback<R>) {
fun <T : CardSessionRunnable<R>, R> startWithRunnable(
iconScanRes: Int? = null,
runnable: T,
callback: CompletionCallback<R>,
) {
if (state != CardSessionState.Inactive) {
val error = TangemSdkError.Busy()
Log.error { "$error" }
Expand All @@ -124,7 +129,7 @@ class CardSession(
prepareSession(runnable) { prepareResult ->
when (prepareResult) {
is CompletionResult.Success -> {
start { _, error ->
start(iconScanRes) { _, error ->
if (error != null) {
Log.error { "$error" }
callback(CompletionResult.Failure(error))
Expand Down Expand Up @@ -156,12 +161,13 @@ class CardSession(

/**
* Starts a card session and performs preflight [ReadCommand].
* @param iconScanRes iconResource to replace on Scan Bottom Sheet
* @param onSessionStarted: callback with the card session. Can contain [TangemSdkError] if something goes wrong.
*/
fun start(onSessionStarted: SessionStartedCallback) {
fun start(iconScanRes: Int? = null, onSessionStarted: SessionStartedCallback) {
Log.session { "start card session with delegate" }
state = CardSessionState.Active
viewDelegate.onSessionStarted(cardId, initialMessage, environment.config.howToIsEnabled)
viewDelegate.onSessionStarted(cardId, initialMessage, environment.config.howToIsEnabled, iconScanRes)

reader.scope = scope
reader.startSession()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class BackupService(
is State.FinalizingPrimaryCard,
is State.FinalizingBackupCard,
-> true

else -> false
}

Expand All @@ -56,6 +57,7 @@ class BackupService(
val passcodeIsSet: Boolean get() = repo.data.passcode != null
val primaryCardIsSet: Boolean get() = repo.data.primaryCard != null
val primaryCardId: String? get() = repo.data.primaryCard?.cardId
val primaryPublicKey: ByteArray? get() = repo.data.primaryCard?.cardPublicKey
val backupCardIds: List<String> get() = repo.data.backupCards.map { it.cardId }

/**
Expand Down Expand Up @@ -126,10 +128,11 @@ class BackupService(
return CompletionResult.Success(Unit)
}

fun proceedBackup(callback: CompletionCallback<Card>) {
fun proceedBackup(iconScanRes: Int? = null, callback: CompletionCallback<Card>) {
when (val currentState = currentState) {
State.FinalizingPrimaryCard ->
handleFinalizePrimaryCard { result -> handleCompletion(result, callback) }
handleFinalizePrimaryCard(iconScanRes = iconScanRes) { result -> handleCompletion(result, callback) }

is State.FinalizingBackupCard ->
handleWriteBackupCard(currentState.index) { result ->
handleCompletion(result, callback)
Expand All @@ -145,7 +148,7 @@ class BackupService(
updateState()
}

fun readPrimaryCard(cardId: String? = null, callback: CompletionCallback<Unit>) {
fun readPrimaryCard(iconScanRes: Int? = null, cardId: String? = null, callback: CompletionCallback<Unit>) {
val formattedCardId = cardId?.let { CardIdFormatter(sdk.config.cardIdDisplayFormat).getFormattedCardId(it) }

val message = formattedCardId?.let {
Expand All @@ -159,12 +162,14 @@ class BackupService(
runnable = StartPrimaryCardLinkingTask(),
cardId = cardId,
initialMessage = message,
iconScanRes = iconScanRes,
) { result ->
when (result) {
is CompletionResult.Success -> {
setPrimaryCard(result.data)
callback(CompletionResult.Success(Unit))
}

is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
Expand All @@ -176,6 +181,7 @@ class BackupService(
updateState()
callback(result)
}

is CompletionResult.Failure -> callback(result)
}
}
Expand All @@ -184,10 +190,13 @@ class BackupService(
currentState = when {
repo.data.accessCode == null || repo.data.primaryCard == null || repo.data.backupCards.isEmpty() ->
State.Preparing

repo.data.attestSignature == null || repo.data.backupData.isEmpty() ->
State.FinalizingPrimaryCard

repo.data.finalizedBackupCardsCount < repo.data.backupCards.size ->
State.FinalizingBackupCard(repo.data.finalizedBackupCardsCount + 1)

else -> {
onBackupCompleted()
State.Finished
Expand Down Expand Up @@ -221,12 +230,13 @@ class BackupService(
addBackupCard(result.data)
callback(CompletionResult.Success(Unit))
}

is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}

private fun handleFinalizePrimaryCard(callback: CompletionCallback<Card>) {
private fun handleFinalizePrimaryCard(iconScanRes: Int? = null, callback: CompletionCallback<Card>) {
try {
if (handleErrors && repo.data.accessCode == null && repo.data.passcode == null) {
throw TangemSdkError.AccessCodeOrPasscodeRequired()
Expand Down Expand Up @@ -278,6 +288,7 @@ class BackupService(
runnable = task,
cardId = primaryCard.cardId,
initialMessage = message,
iconScanRes = iconScanRes,
callback = callback,
)
} catch (error: TangemSdkError) {
Expand Down Expand Up @@ -347,6 +358,7 @@ class BackupService(
)
callback(CompletionResult.Success(result.data))
}

is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class StartPrimaryCardLinkingTask : CardSessionRunnable<PrimaryCard> {
is CompletionResult.Success -> {
loadIssuerSignature(result.data, session, callback)
}

is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
Expand Down Expand Up @@ -59,6 +60,7 @@ class StartPrimaryCardLinkingTask : CardSessionRunnable<PrimaryCard> {
val primaryCard = PrimaryCard(rawCard, issuerSignature = signature.hexToBytes())
callback(CompletionResult.Success(primaryCard))
}

is Result.Failure ->
callback(CompletionResult.Failure(TangemSdkError.IssuerSignatureLoadingFailed()))
}
Expand Down
Loading