mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
Replaced MLKit with ZXing for QR code scanning
This commit is contained in:
parent
9919f716a7
commit
c5e3e4439c
@ -352,7 +352,6 @@ dependencies {
|
|||||||
testImplementation 'org.conscrypt:conscrypt-openjdk-uber:2.5.2' // For Robolectric
|
testImplementation 'org.conscrypt:conscrypt-openjdk-uber:2.5.2' // For Robolectric
|
||||||
testImplementation 'app.cash.turbine:turbine:1.1.0'
|
testImplementation 'app.cash.turbine:turbine:1.1.0'
|
||||||
|
|
||||||
|
|
||||||
implementation 'com.github.bumptech.glide:compose:1.0.0-alpha.5'
|
implementation 'com.github.bumptech.glide:compose:1.0.0-alpha.5'
|
||||||
implementation "androidx.compose.ui:ui:$composeVersion"
|
implementation "androidx.compose.ui:ui:$composeVersion"
|
||||||
implementation "androidx.compose.animation:animation:$composeVersion"
|
implementation "androidx.compose.animation:animation:$composeVersion"
|
||||||
@ -371,7 +370,8 @@ dependencies {
|
|||||||
implementation "androidx.camera:camera-lifecycle:1.3.2"
|
implementation "androidx.camera:camera-lifecycle:1.3.2"
|
||||||
implementation "androidx.camera:camera-view:1.3.2"
|
implementation "androidx.camera:camera-view:1.3.2"
|
||||||
|
|
||||||
implementation "com.google.mlkit:barcode-scanning:17.2.0"
|
// Note: ZXing 3.5.3 is the latest stable release as of 2024/08/21
|
||||||
|
implementation "com.google.zxing:core:$zxingVersion"
|
||||||
}
|
}
|
||||||
|
|
||||||
static def getLastCommitTimestamp() {
|
static def getLastCommitTimestamp() {
|
||||||
|
@ -6,6 +6,7 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
@ -14,7 +15,6 @@ import org.thoughtcrime.securesms.onboarding.manager.LoadAccountManager
|
|||||||
import org.thoughtcrime.securesms.onboarding.messagenotifications.MessageNotificationsActivity
|
import org.thoughtcrime.securesms.onboarding.messagenotifications.MessageNotificationsActivity
|
||||||
import org.thoughtcrime.securesms.ui.setComposeContent
|
import org.thoughtcrime.securesms.ui.setComposeContent
|
||||||
import org.thoughtcrime.securesms.util.start
|
import org.thoughtcrime.securesms.util.start
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class LoadAccountActivity : BaseActionBarActivity() {
|
class LoadAccountActivity : BaseActionBarActivity() {
|
||||||
|
@ -4,6 +4,7 @@ import android.app.Application
|
|||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@ -17,7 +18,6 @@ import org.session.libsignal.crypto.MnemonicCodec
|
|||||||
import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InputTooShort
|
import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InputTooShort
|
||||||
import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InvalidWord
|
import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InvalidWord
|
||||||
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class LoadAccountEvent(val mnemonic: ByteArray)
|
class LoadAccountEvent(val mnemonic: ByteArray)
|
||||||
|
|
||||||
@ -54,6 +54,7 @@ internal class LoadAccountViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onScanQrCode(string: String) {
|
fun onScanQrCode(string: String) {
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
codec.decodeMnemonicOrHexAsByteArray(string).let(::onSuccess)
|
codec.decodeMnemonicOrHexAsByteArray(string).let(::onSuccess)
|
||||||
|
@ -54,7 +54,7 @@ class QRCodeActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onScan(string: String) {
|
private fun onScan(string: String) {
|
||||||
if (!PublicKeyValidation.isValid(string)) {
|
if (!PublicKeyValidation.isValid(string)) {
|
||||||
errors.tryEmit(getString(R.string.this_qr_code_does_not_contain_an_account_id))
|
errors.tryEmit(getString(R.string.this_qr_code_does_not_contain_an_account_id))
|
||||||
} else if (!isFinishing) {
|
} else if (!isFinishing) {
|
||||||
|
@ -47,19 +47,21 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
|||||||
import com.google.accompanist.permissions.isGranted
|
import com.google.accompanist.permissions.isGranted
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import com.google.accompanist.permissions.shouldShowRationale
|
import com.google.accompanist.permissions.shouldShowRationale
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScanner
|
import com.google.zxing.BinaryBitmap
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
import com.google.zxing.ChecksumException
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
import com.google.zxing.FormatException
|
||||||
import com.google.mlkit.vision.barcode.common.Barcode
|
import com.google.zxing.NotFoundException
|
||||||
import com.google.mlkit.vision.common.InputImage
|
import com.google.zxing.PlanarYUVLuminanceSource
|
||||||
|
import com.google.zxing.Result
|
||||||
|
import com.google.zxing.common.HybridBinarizer
|
||||||
|
import com.google.zxing.qrcode.QRCodeReader
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalColors
|
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalType
|
import org.thoughtcrime.securesms.ui.theme.LocalType
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
private const val TAG = "NewMessageFragment"
|
private const val TAG = "NewMessageFragment"
|
||||||
|
|
||||||
@ -137,17 +139,13 @@ fun ScanQrCode(errors: Flow<String>, onScan: (String) -> Unit) {
|
|||||||
runCatching {
|
runCatching {
|
||||||
cameraProvider.get().unbindAll()
|
cameraProvider.get().unbindAll()
|
||||||
|
|
||||||
val options = BarcodeScannerOptions.Builder()
|
|
||||||
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
|
|
||||||
.build()
|
|
||||||
val scanner = BarcodeScanning.getClient(options)
|
|
||||||
|
|
||||||
cameraProvider.get().bindToLifecycle(
|
cameraProvider.get().bindToLifecycle(
|
||||||
LocalLifecycleOwner.current,
|
LocalLifecycleOwner.current,
|
||||||
selector,
|
selector,
|
||||||
preview,
|
preview,
|
||||||
buildAnalysisUseCase(scanner, onScan)
|
buildAnalysisUseCase(QRCodeReader(), onScan)
|
||||||
)
|
)
|
||||||
|
|
||||||
}.onFailure { Log.e(TAG, "error binding camera", it) }
|
}.onFailure { Log.e(TAG, "error binding camera", it) }
|
||||||
|
|
||||||
DisposableEffect(cameraProvider) {
|
DisposableEffect(cameraProvider) {
|
||||||
@ -211,32 +209,51 @@ fun ScanQrCode(errors: Flow<String>, onScan: (String) -> Unit) {
|
|||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
private fun buildAnalysisUseCase(
|
private fun buildAnalysisUseCase(
|
||||||
scanner: BarcodeScanner,
|
scanner: QRCodeReader,
|
||||||
onBarcodeScanned: (String) -> Unit
|
onBarcodeScanned: (String) -> Unit
|
||||||
): ImageAnalysis = ImageAnalysis.Builder()
|
): ImageAnalysis = ImageAnalysis.Builder()
|
||||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
.build().apply {
|
.build().apply {
|
||||||
setAnalyzer(Executors.newSingleThreadExecutor(), Analyzer(scanner, onBarcodeScanned))
|
setAnalyzer(Executors.newSingleThreadExecutor(), QRCodeAnalyzer(scanner, onBarcodeScanned))
|
||||||
}
|
}
|
||||||
|
|
||||||
class Analyzer(
|
class QRCodeAnalyzer(
|
||||||
private val scanner: BarcodeScanner,
|
private val qrCodeReader: QRCodeReader,
|
||||||
private val onBarcodeScanned: (String) -> Unit
|
private val onBarcodeScanned: (String) -> Unit
|
||||||
): ImageAnalysis.Analyzer {
|
): ImageAnalysis.Analyzer {
|
||||||
|
|
||||||
|
// Note: This analyze method is called once per frame of the camera feed.
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
override fun analyze(image: ImageProxy) {
|
override fun analyze(image: ImageProxy) {
|
||||||
InputImage.fromMediaImage(
|
// Grab the image data as a byte array so we can generate a PlanarYUVLuminanceSource from it
|
||||||
image.image!!,
|
val buffer = image.planes[0].buffer
|
||||||
image.imageInfo.rotationDegrees
|
buffer.rewind()
|
||||||
).let(scanner::process).apply {
|
val imageBytes = ByteArray(buffer.capacity())
|
||||||
addOnSuccessListener { barcodes ->
|
buffer.get(imageBytes) // IMPORTANT: This transfers data from the buffer INTO the imageBytes array, although it looks like it would go the other way around!
|
||||||
barcodes.forEach {
|
|
||||||
it.rawValue?.let(onBarcodeScanned)
|
// ZXing requires data as a BinaryBitmap to scan for QR codes, and to generate that we need to feed it a PlanarYUVLuminanceSource
|
||||||
|
val luminanceSource = PlanarYUVLuminanceSource(imageBytes, image.width, image.height, 0, 0, image.width, image.height, false)
|
||||||
|
val binaryBitmap = BinaryBitmap(HybridBinarizer(luminanceSource))
|
||||||
|
|
||||||
|
// Attempt to extract a QR code from the binary bitmap, and pass it through to our `onBarcodeScanned` method if we find one
|
||||||
|
try {
|
||||||
|
val result: Result = qrCodeReader.decode(binaryBitmap)
|
||||||
|
val resultTxt = result.text
|
||||||
|
// No need to close the image here - it'll always make it to the end, and calling
|
||||||
|
// `onBarcodeScanned` with a valid recovery code will stop calling this `analyze` method.
|
||||||
|
onBarcodeScanned(resultTxt)
|
||||||
}
|
}
|
||||||
|
catch (nfe: NotFoundException) { /* Hits if there is no QR code in the image */ }
|
||||||
|
catch (fe: FormatException) { /* Hits if we found a QR code but failed to decode it */ }
|
||||||
|
catch (ce: ChecksumException) { /* Hits if we found a QR code which is corrupted */ }
|
||||||
|
catch (e: Exception) {
|
||||||
|
// Hits if there's a genuine problem
|
||||||
|
Log.e("QR", "error", e)
|
||||||
}
|
}
|
||||||
addOnCompleteListener {
|
|
||||||
|
// Remember to close the image when we're done with it!
|
||||||
|
// IMPORTANT: It is CLOSING the image that allows this method to run again! If we don't
|
||||||
|
// close the image this method runs precisely ONCE and that's it, which is essentially useless.
|
||||||
image.close()
|
image.close()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import com.google.zxing.BarcodeFormat
|
|||||||
import com.google.zxing.EncodeHintType
|
import com.google.zxing.EncodeHintType
|
||||||
import com.google.zxing.qrcode.QRCodeWriter
|
import com.google.zxing.qrcode.QRCodeWriter
|
||||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
|
||||||
object QRCodeUtilities {
|
object QRCodeUtilities {
|
||||||
|
|
||||||
@ -34,5 +35,8 @@ object QRCodeUtilities {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.getOrNull()
|
}.getOrElse {
|
||||||
|
Log.e("QRCodeUtilities", "Failed to generate QR Code", it)
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ phraseVersion=1.2.0
|
|||||||
preferenceVersion=1.2.0
|
preferenceVersion=1.2.0
|
||||||
protobufVersion=2.5.0
|
protobufVersion=2.5.0
|
||||||
testCoreVersion=1.5.0
|
testCoreVersion=1.5.0
|
||||||
|
zxingVersion=3.5.3
|
||||||
android.defaults.buildfeatures.buildconfig=true
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
android.nonTransitiveRClass=false
|
android.nonTransitiveRClass=false
|
||||||
android.nonFinalResIds=false
|
android.nonFinalResIds=false
|
||||||
|
Loading…
Reference in New Issue
Block a user