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

Fix decodeFile Null Crash #556

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Release Notes

## [Unreleased]

### Added
* Show processing error if selfie file is not valid before presenting the image confirmation dialog.

### Fixed
* `BitmapFactory.decodeFile()` operation leading to a crash when the selfie file is null.

## 10.5.1

* Make ConsentInformation optional in EnhancedDocV, EnhancedKYC and BiometricKYC
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.smileidentity.compose.document

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
Expand Down Expand Up @@ -55,6 +56,7 @@ import com.smileidentity.compose.preview.Preview
import com.smileidentity.compose.preview.SmilePreviews
import com.smileidentity.models.v2.Metadatum
import com.smileidentity.util.createDocumentFile
import com.smileidentity.util.isNull
import com.smileidentity.util.isValidDocumentImage
import com.smileidentity.util.toast
import com.smileidentity.util.writeUriToFile
Expand Down Expand Up @@ -155,37 +157,49 @@ fun DocumentCaptureScreen(
)
}

documentImageToConfirm != null -> {
!documentImageToConfirm.isNull() -> {
val painter = remember {
val path = documentImageToConfirm.absolutePath
val path = documentImageToConfirm?.absolutePath
try {
BitmapPainter(BitmapFactory.decodeFile(path).asImageBitmap())
BitmapFactory.decodeFile(path)?.let { bitmap: Bitmap ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think as long as we don't sample this bitmap this will always crash with OOM

  1. We don't need a high quality image for display
  2. This may also need to be done async as well

BitmapPainter(bitmap.asImageBitmap())
} ?: run {
SmileIDCrashReporting.hub.addBreadcrumb(
"Failed to decode document image at $path",
)
ColorPainter(Color.Black)
}
} catch (e: Exception) {
SmileIDCrashReporting.hub.addBreadcrumb("Error loading document image at $path")
SmileIDCrashReporting.hub.addBreadcrumb(
"Error loading document image at $path",
)
SmileIDCrashReporting.hub.captureException(e)
onError(e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jumaallan I think we might have deleted the onError by accident? We should likely keep it to trigger the error screen.

ColorPainter(Color.Black)
}
}
if (showConfirmation) {
ImageCaptureConfirmationDialog(
titleText = stringResource(id = R.string.si_doc_v_confirmation_dialog_title),
subtitleText = stringResource(
id = R.string.si_doc_v_confirmation_dialog_subtitle,
),
painter = painter,
scaleFactor = PREVIEW_SCALE_FACTOR,
confirmButtonText = stringResource(
id = R.string.si_doc_v_confirmation_dialog_confirm_button,
),
onConfirm = { viewModel.onConfirm(documentImageToConfirm, onConfirm) },
retakeButtonText = stringResource(
id = R.string.si_doc_v_confirmation_dialog_retake_button,
),
onRetake = viewModel::onRetry,
)
} else {
viewModel.onConfirm(documentImageToConfirm, onConfirm)
documentImageToConfirm?.let {
if (showConfirmation) {
ImageCaptureConfirmationDialog(
titleText = stringResource(
id = R.string.si_doc_v_confirmation_dialog_title,
),
subtitleText = stringResource(
id = R.string.si_doc_v_confirmation_dialog_subtitle,
),
painter = painter,
scaleFactor = PREVIEW_SCALE_FACTOR,
confirmButtonText = stringResource(
id = R.string.si_doc_v_confirmation_dialog_confirm_button,
),
onConfirm = { viewModel.onConfirm(documentImageToConfirm, onConfirm) },
retakeButtonText = stringResource(
id = R.string.si_doc_v_confirmation_dialog_retake_button,
),
onRetake = viewModel::onRetry,
)
} else {
viewModel.onConfirm(documentImageToConfirm, onConfirm)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
package com.smileidentity.compose.selfie

import android.graphics.Bitmap

Check failure on line 3 in lib/src/main/java/com/smileidentity/compose/selfie/OrchestratedSelfieCaptureScreen.kt

View workflow job for this annotation

GitHub Actions / lint

Unused import
import android.graphics.BitmapFactory

Check failure on line 4 in lib/src/main/java/com/smileidentity/compose/selfie/OrchestratedSelfieCaptureScreen.kt

View workflow job for this annotation

GitHub Actions / lint

Unused import
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box

Check failure on line 6 in lib/src/main/java/com/smileidentity/compose/selfie/OrchestratedSelfieCaptureScreen.kt

View workflow job for this annotation

GitHub Actions / lint

Unused import
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember

Check failure on line 18 in lib/src/main/java/com/smileidentity/compose/selfie/OrchestratedSelfieCaptureScreen.kt

View workflow job for this annotation

GitHub Actions / lint

Unused import
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color

Check failure on line 23 in lib/src/main/java/com/smileidentity/compose/selfie/OrchestratedSelfieCaptureScreen.kt

View workflow job for this annotation

GitHub Actions / lint

Unused import
import androidx.compose.ui.graphics.asImageBitmap

Check failure on line 24 in lib/src/main/java/com/smileidentity/compose/selfie/OrchestratedSelfieCaptureScreen.kt

View workflow job for this annotation

GitHub Actions / lint

Unused import
import androidx.compose.ui.graphics.painter.BitmapPainter

Check failure on line 25 in lib/src/main/java/com/smileidentity/compose/selfie/OrchestratedSelfieCaptureScreen.kt

View workflow job for this annotation

GitHub Actions / lint

Unused import
import androidx.compose.ui.graphics.painter.ColorPainter

Check failure on line 26 in lib/src/main/java/com/smileidentity/compose/selfie/OrchestratedSelfieCaptureScreen.kt

View workflow job for this annotation

GitHub Actions / lint

Unused import
import androidx.compose.ui.res.painterResource

Check failure on line 27 in lib/src/main/java/com/smileidentity/compose/selfie/OrchestratedSelfieCaptureScreen.kt

View workflow job for this annotation

GitHub Actions / lint

Unused import
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.smileidentity.R
import com.smileidentity.SmileIDCrashReporting
import com.smileidentity.compose.components.ImageCaptureConfirmationDialog
import com.smileidentity.compose.components.LocalMetadata
import com.smileidentity.compose.components.ProcessingScreen
import com.smileidentity.models.v2.Metadatum
import com.smileidentity.results.SmartSelfieResult
import com.smileidentity.results.SmileIDCallback
import com.smileidentity.util.isNull
import com.smileidentity.util.randomJobId
import com.smileidentity.util.randomUserId
import com.smileidentity.viewmodel.SelfieViewModel
Expand Down Expand Up @@ -70,67 +78,92 @@
) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
var acknowledgedInstructions by rememberSaveable { mutableStateOf(false) }
Box(
Column (
modifier = modifier
.background(color = MaterialTheme.colorScheme.background)
.windowInsetsPadding(WindowInsets.statusBars)
.consumeWindowInsets(WindowInsets.statusBars)
.fillMaxSize(),
) {
when {
showInstructions && !acknowledgedInstructions -> SmartSelfieInstructionsScreen(
showAttribution = showAttribution,
) {
acknowledgedInstructions = true
}
Text("The count is ${uiState.counttt}")
SelfieCaptureScreen(
userId = userId,
jobId = jobId,
isEnroll = isEnroll,
allowAgentMode = allowAgentMode,
skipApiSubmission = skipApiSubmission,
)

uiState.processingState != null -> ProcessingScreen(
processingState = uiState.processingState,
inProgressTitle = stringResource(R.string.si_smart_selfie_processing_title),
inProgressSubtitle = stringResource(R.string.si_smart_selfie_processing_subtitle),
inProgressIcon = painterResource(R.drawable.si_smart_selfie_processing_hero),
successTitle = stringResource(R.string.si_smart_selfie_processing_success_title),
successSubtitle = uiState.errorMessage.resolve().takeIf { it.isNotEmpty() }
?: stringResource(R.string.si_smart_selfie_processing_success_subtitle),
successIcon = painterResource(R.drawable.si_processing_success),
errorTitle = stringResource(R.string.si_smart_selfie_processing_error_title),
errorSubtitle = uiState.errorMessage.resolve().takeIf { it.isNotEmpty() }
?: stringResource(id = R.string.si_processing_error_subtitle),
errorIcon = painterResource(R.drawable.si_processing_error),
continueButtonText = stringResource(R.string.si_continue),
onContinue = { viewModel.onFinished(onResult) },
retryButtonText = stringResource(R.string.si_smart_selfie_processing_retry_button),
onRetry = viewModel::onRetry,
closeButtonText = stringResource(R.string.si_smart_selfie_processing_close_button),
onClose = { viewModel.onFinished(onResult) },
)

uiState.selfieToConfirm != null -> ImageCaptureConfirmationDialog(
titleText = stringResource(R.string.si_smart_selfie_confirmation_dialog_title),
subtitleText = stringResource(
R.string.si_smart_selfie_confirmation_dialog_subtitle,
),
painter = BitmapPainter(
BitmapFactory.decodeFile(uiState.selfieToConfirm.absolutePath).asImageBitmap(),
),
confirmButtonText = stringResource(
R.string.si_smart_selfie_confirmation_dialog_confirm_button,
),
onConfirm = viewModel::submitJob,
retakeButtonText = stringResource(
R.string.si_smart_selfie_confirmation_dialog_retake_button,
),
onRetake = viewModel::onSelfieRejected,
scaleFactor = 1.25f,
)

else -> SelfieCaptureScreen(
userId = userId,
jobId = jobId,
isEnroll = isEnroll,
allowAgentMode = allowAgentMode,
skipApiSubmission = skipApiSubmission,
)
}
// when {
// showInstructions && !acknowledgedInstructions -> SmartSelfieInstructionsScreen(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undo/delete?

// showAttribution = showAttribution,
// ) {
// acknowledgedInstructions = true
// }
//
// uiState.processingState != null -> ProcessingScreen(
// processingState = uiState.processingState,
// inProgressTitle = stringResource(R.string.si_smart_selfie_processing_title),
// inProgressSubtitle = stringResource(R.string.si_smart_selfie_processing_subtitle),
// inProgressIcon = painterResource(R.drawable.si_smart_selfie_processing_hero),
// successTitle = stringResource(R.string.si_smart_selfie_processing_success_title),
// successSubtitle = uiState.errorMessage.resolve().takeIf { it.isNotEmpty() }
// ?: stringResource(R.string.si_smart_selfie_processing_success_subtitle),
// successIcon = painterResource(R.drawable.si_processing_success),
// errorTitle = stringResource(R.string.si_smart_selfie_processing_error_title),
// errorSubtitle = uiState.errorMessage.resolve().takeIf { it.isNotEmpty() }
// ?: stringResource(id = R.string.si_processing_error_subtitle),
// errorIcon = painterResource(R.drawable.si_processing_error),
// continueButtonText = stringResource(R.string.si_continue),
// onContinue = { viewModel.onFinished(onResult) },
// retryButtonText = stringResource(R.string.si_smart_selfie_processing_retry_button),
// onRetry = viewModel::onRetry,
// closeButtonText = stringResource(R.string.si_smart_selfie_processing_close_button),
// onClose = { viewModel.onFinished(onResult) },
// )
//
// // !uiState.selfieToConfirm.isNull() -> ImageCaptureConfirmationDialog(
// // titleText = stringResource(R.string.si_smart_selfie_confirmation_dialog_title),
// // subtitleText = stringResource(
// // R.string.si_smart_selfie_confirmation_dialog_subtitle,
// // ),
// // painter = remember {
// // val path = uiState.selfieToConfirm?.absolutePath
// // try {
// // BitmapFactory.decodeFile(path)?.let { bitmap: Bitmap ->
// // BitmapPainter(bitmap.asImageBitmap())
// // } ?: run {
// // SmileIDCrashReporting.hub.addBreadcrumb(
// // "Failed to decode selfie image at $path",
// // )
// // ColorPainter(Color.Black)
// // }
// // } catch (e: Exception) {
// // SmileIDCrashReporting.hub.addBreadcrumb(
// // "Error loading selfie image at $path",
// // )
// // SmileIDCrashReporting.hub.captureException(e)
// // ColorPainter(Color.Black)
// // }
// // },
// // confirmButtonText = stringResource(
// // R.string.si_smart_selfie_confirmation_dialog_confirm_button,
// // ),
// // onConfirm = viewModel::submitJob,
// // retakeButtonText = stringResource(
// // R.string.si_smart_selfie_confirmation_dialog_retake_button,
// // ),
// // onRetake = viewModel::onSelfieRejected,
// // scaleFactor = 1.25f,
// // )
//
// else -> SelfieCaptureScreen(
// userId = userId,
// jobId = jobId,
// isEnroll = isEnroll,
// allowAgentMode = allowAgentMode,
// skipApiSubmission = skipApiSubmission,
// )
// }
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.smileidentity.compose.selfie

import android.graphics.BitmapFactory
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
Expand All @@ -18,6 +19,7 @@ import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
Expand All @@ -28,6 +30,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.draw.scale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
Expand Down Expand Up @@ -88,6 +91,11 @@ fun SelfieCaptureScreen(
val faceFillPercent = remember { MAX_FACE_AREA_THRESHOLD * viewfinderZoom * 2 }
// Force maximum brightness in order to light up the user's face
ForceBrightness()
val context = LocalContext.current
LaunchedEffect(viewModel) {
viewModel.juma(bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.batman))
}

Box(modifier = modifier.fillMaxSize()) {
CameraPreview(
cameraState = cameraState,
Expand Down
10 changes: 10 additions & 0 deletions lib/src/main/java/com/smileidentity/util/FileUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -528,4 +528,14 @@ internal fun createAuthenticationRequestFile(
return file
}

internal fun File?.isNull(): Boolean {
if (this == null) return false

if (!this.exists() || !this.isFile || this.length() <= 0) {
return false
}

return true
}

enum class DeleteScope { Unsubmitted, Submitted, All }
Loading
Loading