@@ -36,6 +36,9 @@ import androidx.appcompat.app.AppCompatActivity
3636import androidx.core.app.ActivityCompat
3737import androidx.core.content.FileProvider
3838import androidx.core.view.WindowCompat
39+ import com.google.mlkit.vision.barcode.common.Barcode
40+ import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
41+ import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
3942import com.google.zxing.BarcodeFormat
4043import com.google.zxing.BinaryBitmap
4144import com.google.zxing.DecodeHintType
@@ -530,17 +533,17 @@ class MainActivity : AppCompatActivity() {
530533 .show()
531534 }
532535
533- // Three independent ways to ingest a QR config: live camera, picking an
534- // image from gallery (decoded offline), or pasting the plain text payload
535- // shown next to the QR in the dashboard. Camera fails on dense QRs +
536- // weak autofocus, so the alternatives matter for real-world use .
536+ // Three independent ways to ingest a QR config: live camera (Google ML
537+ // Kit code scanner — way more robust than ZXing on stylized/dense QRs),
538+ // picking an image from gallery (decoded via ZXing offline), or pasting
539+ // the plain text payload shown next to the QR in the dashboard .
537540 private fun showQrSourceChooser () {
538- val items = arrayOf(" Camera (live scan )" , " Pick QR image from gallery" , " Paste from clipboard" )
541+ val items = arrayOf(" Camera (Google scanner )" , " Pick QR image from gallery" , " Paste from clipboard" )
539542 AlertDialog .Builder (this )
540543 .setTitle(" Import tunnel QR" )
541544 .setItems(items) { _, which ->
542545 when (which) {
543- 0 -> qrLauncher.launch(buildScanOptions() )
546+ 0 -> launchGoogleCodeScanner( )
544547 1 -> qrImageLauncher.launch(" image/*" )
545548 2 -> applyClipboardQr()
546549 }
@@ -549,6 +552,39 @@ class MainActivity : AppCompatActivity() {
549552 .show()
550553 }
551554
555+ // Google ML Kit code scanner — Play-Services-backed, no CAMERA permission
556+ // needed, handles stylized/dense QRs that ZXing struggles with. Falls back
557+ // to ZXing when Play Services is missing or the scan client errors.
558+ private fun launchGoogleCodeScanner () {
559+ try {
560+ val options = GmsBarcodeScannerOptions .Builder ()
561+ .setBarcodeFormats(Barcode .FORMAT_QR_CODE )
562+ .build()
563+ val scanner = GmsBarcodeScanning .getClient(this , options)
564+ scanner.startScan()
565+ .addOnSuccessListener { barcode ->
566+ val text = barcode.rawValue
567+ if (text.isNullOrBlank()) {
568+ Toast .makeText(this , " QR: empty payload" , Toast .LENGTH_SHORT ).show()
569+ } else {
570+ applyQrPayload(text)
571+ }
572+ }
573+ .addOnCanceledListener {
574+ Toast .makeText(this , " QR scan cancelled" , Toast .LENGTH_SHORT ).show()
575+ }
576+ .addOnFailureListener { e ->
577+ Toast .makeText(this , " Google scanner failed (${e.message} ); falling back to ZXing" ,
578+ Toast .LENGTH_LONG ).show()
579+ qrLauncher.launch(buildScanOptions())
580+ }
581+ } catch (e: Throwable ) {
582+ Toast .makeText(this , " No Play Services scanner; falling back to ZXing" ,
583+ Toast .LENGTH_SHORT ).show()
584+ qrLauncher.launch(buildScanOptions())
585+ }
586+ }
587+
552588 // setOrientationLocked(true) + custom portrait CaptureActivity prevents the
553589 // library's default sensorLandscape activity from showing the "rotate the
554590 // phone" overlay on portrait-only apps.
0 commit comments