Handle scan recovery phrase QR

This commit is contained in:
Andrew 2024-03-01 18:46:24 +10:30
parent 2555b34d8e
commit 9e270c7ac0
2 changed files with 58 additions and 13 deletions

View File

@ -7,6 +7,8 @@ import android.os.Bundle
import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.camera.core.CameraSelector import androidx.camera.core.CameraSelector
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Preview import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView import androidx.camera.view.PreviewView
@ -59,19 +61,28 @@ 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.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
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
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.ui.AppTheme import org.thoughtcrime.securesms.ui.AppTheme
import org.thoughtcrime.securesms.ui.OutlineButton import org.thoughtcrime.securesms.ui.OutlineButton
import org.thoughtcrime.securesms.ui.baseBold import org.thoughtcrime.securesms.ui.baseBold
import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.colorDestructive
import java.util.concurrent.Executors
import javax.inject.Inject import javax.inject.Inject
private const val TAG = "LinkDeviceActivity"
@AndroidEntryPoint @AndroidEntryPoint
@androidx.annotation.OptIn(ExperimentalGetImage::class)
class LinkDeviceActivity : BaseActionBarActivity() { class LinkDeviceActivity : BaseActionBarActivity() {
@Inject @Inject
@ -134,16 +145,20 @@ class LinkDeviceActivity : BaseActionBarActivity() {
val localContext = LocalContext.current val localContext = LocalContext.current
val cameraProvider = remember { ProcessCameraProvider.getInstance(localContext) } val cameraProvider = remember { ProcessCameraProvider.getInstance(localContext) }
val options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build()
val scanner = BarcodeScanning.getClient(options)
runCatching { runCatching {
when (title) { when (title) {
R.string.activity_link_device_scan_qr_code -> { R.string.activity_link_device_scan_qr_code -> {
cameraProvider.get().bindToLifecycle(LocalLifecycleOwner.current, selector, preview)
LocalSoftwareKeyboardController.current?.hide() LocalSoftwareKeyboardController.current?.hide()
cameraProvider.get().bindToLifecycle(LocalLifecycleOwner.current, selector, preview, buildAnalysisUseCase(scanner, viewModel::onQrPhrase))
} }
else -> cameraProvider.get().unbind(preview) else -> cameraProvider.get().unbind(preview)
} }
} }.onFailure { Log.e(TAG, "error binding camera", it) }
when (title) { when (title) {
R.string.activity_recovery_password -> RecoveryPassword(state, onChange, onContinue) R.string.activity_recovery_password -> RecoveryPassword(state, onChange, onContinue)
R.string.activity_link_device_scan_qr_code -> MaybeScanQrCode() R.string.activity_link_device_scan_qr_code -> MaybeScanQrCode()
@ -167,7 +182,7 @@ class LinkDeviceActivity : BaseActionBarActivity() {
) { ) {
Text( Text(
"Camera Permission permanently denied. Configure in settings.", "Camera Permission permanently denied. Configure in settings.",
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
Spacer(modifier = Modifier.size(20.dp)) Spacer(modifier = Modifier.size(20.dp))
OutlineButton( OutlineButton(
@ -196,11 +211,7 @@ fun ScanQrCode(preview: Preview) {
Box { Box {
AndroidView( AndroidView(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
factory = { context -> factory = { PreviewView(it).apply { preview.setSurfaceProvider(surfaceProvider) } }
PreviewView(context).apply {
preview.setSurfaceProvider(surfaceProvider)
}
}
) )
Box( Box(
@ -270,3 +281,26 @@ fun RecoveryPassword(state: LinkDeviceState, onChange: (String) -> Unit = {}, on
fun Context.startLinkDeviceActivity() { fun Context.startLinkDeviceActivity() {
Intent(this, LinkDeviceActivity::class.java).let(::startActivity) Intent(this, LinkDeviceActivity::class.java).let(::startActivity)
} }
private fun buildAnalysisUseCase(
scanner: BarcodeScanner,
onBarcodeScanned: (String) -> Unit
): ImageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build().apply {
setAnalyzer(Executors.newSingleThreadExecutor()) { imageProxy ->
InputImage.fromMediaImage(
imageProxy.image!!,
imageProxy.imageInfo.rotationDegrees
).let(scanner::process).apply {
addOnSuccessListener { barcodes ->
barcodes.forEach {
it.takeIf { it.valueType == Barcode.TYPE_TEXT }?.rawValue?.let(onBarcodeScanned)
}
}
addOnCompleteListener {
imageProxy.close()
}
}
}
}

View File

@ -30,7 +30,14 @@ class LinkDeviceViewModel @Inject constructor(
fun onRecoveryPhrase() { fun onRecoveryPhrase() {
val mnemonic = state.value.recoveryPhrase val mnemonic = state.value.recoveryPhrase
tryPhrase(mnemonic)
}
fun onQrPhrase(string: String) {
tryPhrase(string)
}
private fun tryPhrase(mnemonic: String) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
MnemonicCodec { MnemonicUtilities.loadFileContents(getApplication(), it) } MnemonicCodec { MnemonicUtilities.loadFileContents(getApplication(), it) }
@ -39,10 +46,14 @@ class LinkDeviceViewModel @Inject constructor(
.let(::LinkDeviceEvent) .let(::LinkDeviceEvent)
.let { event.send(it) } .let { event.send(it) }
} catch (exception: Exception) { } catch (exception: Exception) {
when (exception) { state.update {
is MnemonicCodec.DecodingError -> exception.description it.copy(
else -> "An error occurred." error = when (exception) {
}.let { error -> state.update { it.copy(error = error) } } is MnemonicCodec.DecodingError -> exception.description
else -> "An error occurred."
}
)
}
} }
} }
} }