Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import android.view.View
import android.view.ViewPropertyAnimator
import androidx.activity.result.contract.ActivityResultContracts
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
Expand Down Expand Up @@ -77,6 +80,7 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex
private var previousPermissionStatus: PermissionStatus? = null
private lateinit var cameraExecutor: ExecutorService
private lateinit var imageAnalysis: ImageAnalysis
private lateinit var imageCapture: ImageCapture
private var progressAnimator: ViewPropertyAnimator? = null
private var checkAnimator: ViewPropertyAnimator? = null
private var isAnimatingCompletion: Boolean = false
Expand Down Expand Up @@ -186,10 +190,13 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex
cameraProviderFuture = cameraProviderFuture,
surfaceProvider = binding.preview.surfaceProvider,
viewLifecycleOwner = viewLifecycleOwner,
onImageAnalysisReady = {
imageAnalysis = it
onImageAnalysisReady = { analysis ->
imageAnalysis = analysis
onComplete()
},
onImageCaptureReady = { capture ->
imageCapture = capture
},
)
cameraProviderFuture.addListener(cameraListener, ContextCompat.getMainExecutor(requireContext()))
}
Expand Down Expand Up @@ -278,22 +285,39 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex
return@setAnalyzer
}
viewModel.ocrOnFrameStarted()
lifecycleScope.launch(bgDispatcher) {
try {
val (bitmap, imageInfo) = imageProxy.toBitmap() to imageProxy.imageInfo
val cropConfig: OcrCropConfig = buildOcrCropConfigUseCase(
rotationDegrees = imageInfo.rotationDegrees,
cameraPreview = binding.preview,
documentScannerArea = binding.documentScannerArea,
)
viewModel.runOcrOnFrame(frame = bitmap, cropConfig)
} finally {
imageProxy.close()
}
}
// Running OCR as often as we can while camera feedback is displayed to the user
captureFullResImageForOcr()
imageProxy.close()
}
}

private fun captureFullResImageForOcr() {
imageCapture.takePicture(
cameraExecutor,
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(imageProxy: ImageProxy) {
lifecycleScope.launch(bgDispatcher) {
try {
val (bitmap, imageInfo) = imageProxy.toBitmap() to imageProxy.imageInfo
val cropConfig: OcrCropConfig = buildOcrCropConfigUseCase(
rotationDegrees = imageInfo.rotationDegrees,
cameraPreview = binding.preview,
documentScannerArea = binding.documentScannerArea,
)
viewModel.runOcrOnFrame(frame = bitmap, cropConfig)
} finally {
imageProxy.close()
}
}
}

override fun onError(e: ImageCaptureException) {
Simber.e("Photo capture failed in OCR", e, MULTI_FACTOR_ID)
}
},
)
}

private fun stopCamera() {
if (::cameraExecutor.isInitialized) {
cameraExecutor.shutdown()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ internal class ExternalCredentialScanOcrViewModel @AssistedInject constructor(
@DispatcherBG private val bgDispatcher: CoroutineDispatcher,
) : ViewModel() {
@AssistedFactory
interface Factory {
fun interface Factory {
fun create(ocrDocumentType: OcrDocumentType): ExternalCredentialScanOcrViewModel
}

Expand Down Expand Up @@ -168,6 +168,6 @@ internal class ExternalCredentialScanOcrViewModel @AssistedInject constructor(
}

companion object {
private const val SUCCESSFUL_SCANS_REQUIRED = 5
private const val SUCCESSFUL_SCANS_REQUIRED = 3
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal class ProvideCameraListenerUseCase @Inject constructor() {
surfaceProvider: Preview.SurfaceProvider,
viewLifecycleOwner: LifecycleOwner,
onImageAnalysisReady: (ImageAnalysis) -> Unit,
onImageCaptureReady: (ImageCapture) -> Unit,
) = Runnable {
val cameraProvider = cameraProviderFuture.get()
val aspectRatio = AspectRatio.RATIO_16_9
Expand Down Expand Up @@ -49,6 +50,7 @@ internal class ProvideCameraListenerUseCase @Inject constructor() {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(viewLifecycleOwner, cameraSelector, preview, imageCapture, imageAnalysis)
onImageAnalysisReady(imageAnalysis)
onImageCaptureReady(imageCapture)
} catch (e: Exception) {
Simber.e("Camera binding failed in OCR", e, MULTI_FACTOR_ID)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ internal class ExternalCredentialScanOcrViewModelTest {
val state = observer.value() as ScanOcrState.ScanningInProgress
assertThat(state.ocrDocumentType).isEqualTo(documentType)
assertThat(state.successfulCaptures).isEqualTo(0)
assertThat(state.scansRequired).isEqualTo(5)
assertThat(state.scansRequired).isEqualTo(3)
}

@Test
Expand Down