fix(android): use Google Code Scanner for onboarding QR

This commit is contained in:
Ayaan Zaidi 2026-03-13 16:32:11 +05:30
parent b72c87712d
commit b934cb49c7
2 changed files with 39 additions and 28 deletions

View File

@ -194,7 +194,7 @@ dependencies {
implementation("androidx.camera:camera-lifecycle:1.5.2")
implementation("androidx.camera:camera-video:1.5.2")
implementation("androidx.camera:camera-view:1.5.2")
implementation("com.journeyapps:zxing-android-embedded:4.3.0")
implementation("com.google.android.gms:play-services-code-scanner:16.1.0")
// Unicast DNS-SD (Wide-Area Bonjour) for tailnet discovery domains.
implementation("dnsjava:dnsjava:3.6.4")

View File

@ -96,8 +96,9 @@ import ai.openclaw.app.LocationMode
import ai.openclaw.app.MainViewModel
import ai.openclaw.app.R
import ai.openclaw.app.node.DeviceNotificationListenerService
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
private enum class OnboardingStep(val index: Int, val label: String) {
Welcome(1, "Welcome"),
@ -241,6 +242,13 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
var attemptedConnect by rememberSaveable { mutableStateOf(false) }
val lifecycleOwner = LocalLifecycleOwner.current
val qrScannerOptions =
remember {
GmsBarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build()
}
val qrScanner = remember(context, qrScannerOptions) { GmsBarcodeScanning.getClient(context, qrScannerOptions) }
val smsAvailable =
remember(context) {
@ -460,23 +468,6 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
val qrScanLauncher =
rememberLauncherForActivityResult(ScanContract()) { result ->
val contents = result.contents?.trim().orEmpty()
if (contents.isEmpty()) {
return@rememberLauncherForActivityResult
}
val scannedSetupCode = resolveScannedSetupCode(contents)
if (scannedSetupCode == null) {
gatewayError = "QR code did not contain a valid setup code."
return@rememberLauncherForActivityResult
}
setupCode = scannedSetupCode
gatewayInputMode = GatewayInputMode.SetupCode
gatewayError = null
attemptedConnect = false
}
if (pendingTrust != null) {
val prompt = pendingTrust!!
AlertDialog(
@ -552,14 +543,28 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
gatewayError = gatewayError,
onScanQrClick = {
gatewayError = null
qrScanLauncher.launch(
ScanOptions().apply {
setDesiredBarcodeFormats(ScanOptions.QR_CODE)
setPrompt("Scan OpenClaw onboarding QR")
setBeepEnabled(false)
setOrientationLocked(false)
},
)
qrScanner.startScan()
.addOnSuccessListener { barcode ->
val contents = barcode.rawValue?.trim().orEmpty()
if (contents.isEmpty()) {
return@addOnSuccessListener
}
val scannedSetupCode = resolveScannedSetupCode(contents)
if (scannedSetupCode == null) {
gatewayError = "QR code did not contain a valid setup code."
return@addOnSuccessListener
}
setupCode = scannedSetupCode
gatewayInputMode = GatewayInputMode.SetupCode
gatewayError = null
attemptedConnect = false
}
.addOnCanceledListener {
// User dismissed the scanner; preserve current form state.
}
.addOnFailureListener { error ->
gatewayError = resolveQrScannerError(error)
}
},
onAdvancedOpenChange = { gatewayAdvancedOpen = it },
onInputModeChange = {
@ -1785,6 +1790,12 @@ private fun isPermissionGranted(context: Context, permission: String): Boolean {
return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
}
private fun resolveQrScannerError(error: Exception): String {
val detail = error.message?.trim().orEmpty()
val prefix = "Google Code Scanner could not start. Update Google Play services or use the setup code manually."
return if (detail.isEmpty()) prefix else "$prefix ($detail)"
}
private fun isNotificationListenerEnabled(context: Context): Boolean {
return DeviceNotificationListenerService.isAccessEnabled(context)
}