Scan without asking the user to frame or turn the phone

The library's CaptureActivity has three defaults that put work on the
person holding the phone, so the scan now goes through our own subclass
instead:

- It decodes only the framing rectangle, inset 10% from every edge. A code
  filling the viewfinder still decodes -- measured against the library's
  own decoder rather than assumed -- but only by spending its quiet zone
  on the crop, and anything further out is never looked at. The whole
  preview is decoded now, so framing is not something to get right.
- It draws a red laser line and scatters dots wherever the detector finds
  a candidate pattern. That is the library's house style, and it looks
  like a fault. The preview is plain now.
- Its manifest entry pins the activity to landscape. The code being
  scanned is usually on a monitor in front of someone holding the phone
  upright, so ours follows the sensor.

Verified on the emulator: the scanner opens on a bare preview with no
laser, no dots and no status line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQz6R4kBQcWSHBNZMgBnjL
This commit is contained in:
irisandClaude Opus 5 committed 2026-08-25 20:54:15 -04:00
1 parent 0522123a33
commit bf82166736
4 files changed
+55 -1

No files matched your search

+3 -1
View File
@@ -158,7 +158,9 @@ Established 2026-08-25, and it decides more than it looks like:
silently never matches — while the phone's own camera app, which tries silently never matches — while the phone's own camera app, which tries
both, does. The scanner asks for `Intents.Scan.MIXED_SCAN`, which both, does. The scanner asks for `Intents.Scan.MIXED_SCAN`, which
alternates normal and inverted frames; keep it that way rather than alternates normal and inverted frames; keep it that way rather than
making the server dictate the colours. making the server dictate the colours. `EnrollmentScanActivity` also
turns off the library's 10% framing-rect inset (it decodes only what is
inside it) and its laser/result-point decorations.
- **AGP 9 refuses `Provider`s in the source-set API**: generated sources go - **AGP 9 refuses `Provider`s in the source-set API**: generated sources go
through `androidComponents.onVariants { it.sources.java?.addGenerated through `androidComponents.onVariants { it.sources.java?.addGenerated
SourceDirectory(task, Task::outputDir) }`, which also carries the task SourceDirectory(task, Task::outputDir) }`, which also carries the task
@@ -40,5 +40,18 @@
<data android:scheme="aiapp" android:host="enroll" /> <data android:scheme="aiapp" android:host="enroll" />
</intent-filter> </intent-filter>
</activity> </activity>
<!-- The scanner behind Settings' "Scan QR code". Declared here so
it can drop the library CaptureActivity's landscape pin: the
code being scanned is usually on a monitor in front of someone
holding the phone upright. zxing_CaptureTheme is the library's
own fullscreen theme, which is all the activity needs. -->
<activity
android:name=".EnrollmentScanActivity"
android:clearTaskOnLaunch="true"
android:screenOrientation="fullSensor"
android:stateNotNeeded="true"
android:theme="@style/zxing_CaptureTheme"
android:windowSoftInputMode="stateAlwaysHidden" />
</application> </application>
</manifest> </manifest>
@@ -0,0 +1,36 @@
package com.example.aiapp
import android.view.View
import com.journeyapps.barcodescanner.CaptureActivity
import com.journeyapps.barcodescanner.DecoratedBarcodeView
/**
* The screen behind the Settings screen's "Scan QR code" button:
* zxing-android-embedded's capture activity with two of its defaults taken
* off, both because they put work on the person holding the phone.
*
* - **It decodes only the framing rectangle**, which
* `CameraPreview.marginFraction` insets by 10% from every edge and
* `DecoderThread` crops each frame to before the decoder sees it. A code
* that fills the viewfinder keeps decoding -- measured, not assumed --
* but it does so having spent its quiet zone and margin for error on
* the crop, and anything further out is simply not looked at. Decoding
* the whole preview costs nothing and means the framing is never the
* user's problem.
* - **It decorates the preview** with a red laser line and the dots the
* detector scatters wherever it finds a candidate pattern. That is the
* library's house style; a plain preview is ours.
*
* Orientation is left to the sensor rather than pinned to landscape as the
* library's own manifest entry pins it, so the phone can be held whichever
* way the code is in front of it.
*/
class EnrollmentScanActivity : CaptureActivity() {
override fun initializeContent(): DecoratedBarcodeView {
val view = super.initializeContent()
view.barcodeView.marginFraction = 0.0
view.viewFinder.visibility = View.GONE
view.statusView.visibility = View.GONE
return view
}
}
@@ -87,6 +87,9 @@ fun SettingsScreen(
scanLauncher.launch( scanLauncher.launch(
ScanOptions() ScanOptions()
.setDesiredBarcodeFormats(ScanOptions.QR_CODE) .setDesiredBarcodeFormats(ScanOptions.QR_CODE)
.setCaptureActivity(EnrollmentScanActivity::class.java)
// Follow the phone, not the library's landscape pin.
.setOrientationLocked(false)
// Decode both polarities: ZXing otherwise looks only for a // Decode both polarities: ZXing otherwise looks only for a
// dark code on a light ground, and ai-server's QR is block // dark code on a light ground, and ai-server's QR is block
// characters in the terminal's foreground colour -- so on a // characters in the terminal's foreground colour -- so on a