Skip to content

feat(android): let products scan codes with the app's scanner - #1319

Merged
valentinfernandez1 merged 20 commits into
feat/host-scanner-corefrom
feat/host-scanner-android
Oct 9, 2026
Merged

valentinfernandez1 merged 20 commits into
feat/host-scanner-corefrom
feat/host-scanner-android

Conversation

@valentinfernandez1

@valentinfernandez1 valentinfernandez1 commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Host scanner on Android

The Android app serves scanner.scan. A product asks to scan, the app opens its own scanner, and the product gets back the code.

What the user sees

  • The same scanner and frame as the app's own QR scanner, as a full-height sheet over the product.
  • The title "Scan for ", with the product's hint underneath on one line.
  • A short "This code isn't for " message when the camera reads a code the product did not ask for. Scanning keeps going.
  • Closing the sheet or pressing back answers Dismissed.
  • Refusing the camera answers CameraUnavailable. If it was refused for good, the app first shows its usual "open Settings" dialog.

How it works

  • TrUAPIProductScans serves the scanner and is installed with setScanner.
  • An App gets NotVisible, and no scanner opens, unless one of its pages is on screen in the focused window. A page in a background tab, under a host sheet, or in a backgrounded app does not count. The check waits up to a second for the page to get focus back, since a closing host screen, such as the last scanner, holds it for a moment. A Worker gets NotVisible unless the app is in front. The core already checked that the user tapped its card.
  • A scan waiting behind another one is checked when its turn comes, since the user may have left by then.
  • Every code the camera reads goes through the core's ScanFilter, so Android accepts the same codes as every other host.
  • ML Kit reads the formats the product asked for. UPC-A is reported as EAN-13 with a leading zero, as on iOS.
  • The scanner is a sheet, not a screen, so it opens over the product sheet without closing it.

Shared pieces

  • QrViewfinder and the scanner frame move from the scan feature to common, so both scanners draw the same thing.
  • QrCodeAnalyzer takes the formats to read and reports every code in a frame with its format. The existing QR-only callers are unchanged.
  • TrUAPIPrompts shows one kind of host screen at a time. It closes the screen when the product cancels, and fails the call if the screen never appears. The contact picker uses it too, so it now closes on cancel instead of staying open.

Testing

  • Unit tests for the prompt helper, the format mapping, the scanner's view model and the scanner's visibility rules.
  • :feature:products:impl:testReleaseUnitTest, :app:compileVanillaDebugKotlin and detekt pass locally.
  • On the emulator, with a test product that has a Scan button on its Pocket card (a Worker) and on its own page (an App), and the emulator camera showing a QR image:
    • Both buttons open the scanner with the product's title and hint, and get back the scanned code.
    • A code without the product's prefix shows "This code isn't for " and the scanner stays open.
    • The close button and back answer Dismissed.
    • Refusing the camera answers CameraUnavailable.

…any format

QrViewfinder, PermissionMissingHint and the scanner frame move from the scan
feature to common, so other features draw the same scanner. QrCodeAnalyzer
takes the ML Kit formats to read and reports the format of each code; the
QR-only constructor keeps existing callers unchanged.
TrUAPIPrompts shows one kind of host screen at a time, answers a screen that
never appears instead of holding the core, and closes the screen when the
call is cancelled. The contact picker moves onto it, replacing its own
context, holder and launcher, which left the picker open after a cancel.
AppScannerHostBridge serves scanner.scan with a full-screen viewfinder titled
for the product, with its hint underneath. Every code goes through the core's
ScanFilter: a match answers once, a wrong code shows a short message and
scanning goes on. An App gets NotVisible unless one of its pages is on screen
in the focused window. UPC-A is reported as EAN-13, as on iOS.
@valentinfernandez1 valentinfernandez1 added the rust Pull requests that update rust code label Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

This pull request touches an app, which is not built by default. Add a label for each build you want:

  • Android build, android-device-build: attaches an APK that installs on an Android phone or an emulator.

Each starts as soon as it is added and follows the branch from then on.

@github-actions github-actions Bot added the host-android Touches the Android host tree label Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Bundle size report

Compared with main at 6843c76.

Raw Gzip Brotli
truapi-host 7.86 MiB (+8.9 KiB, +0.1%) 5.65 MiB (+2.9 KiB, +0.1%) 5.38 MiB (+2.3 KiB)
truapi-provider 4.03 MiB 1.27 MiB 959.8 KiB
truapi 6.01 MiB (+78 B) 849.3 KiB (+48 B) 695.9 KiB (-8 B)
Total 17.90 MiB (+9.0 KiB) 7.75 MiB (+2.9 KiB) 7.00 MiB (+2.3 KiB)

WebAssembly modules

Raw Gzip Brotli
truapi-host/wasm/web/truapi_server_bg.wasm 2.75 MiB (+8.1 KiB, +0.3%) 980.8 KiB (+2.7 KiB, +0.3%) 739.5 KiB (+2.2 KiB, +0.3%)
truapi-host/wasm/web/truapi_verifiable_bg.wasm 4.89 MiB 4.64 MiB 4.61 MiB
truapi-provider/truapi_provider_bg.wasm 3.99 MiB 1.26 MiB 952.1 KiB
Changed files (7)
Raw Gzip Brotli
truapi-host/wasm/web/truapi_server_bg.wasm 2.75 MiB (+8.1 KiB, +0.3%) 980.8 KiB (+2.7 KiB, +0.3%) 739.5 KiB (+2.2 KiB, +0.3%)
truapi-host/generated/host-callbacks-adapter.js 6.9 KiB (+300 B, +4.4%) 1.5 KiB (+52 B, +3.4%) 1.4 KiB (+47 B, +3.5%)
truapi-host/generated/worker-callbacks.js 7.2 KiB (+266 B, +3.8%) 1.5 KiB (+32 B, +2.2%) 1.2 KiB (+9 B, +0.7%)
truapi-host/generated/host-callbacks.js 10.5 KiB (+235 B, +2.2%) 3.4 KiB (+63 B, +1.9%) 2.8 KiB (+34 B, +1.2%)
truapi-host/web/create-worker-host-runtime.js 54.0 KiB (+61 B, +0.1%) 11.8 KiB (+11 B, +0.1%) 10.3 KiB (+5 B)
truapi/explorer/codegen/types.js 214.0 KiB (+39 B) 29.7 KiB (+28 B, +0.1%) 23.8 KiB (+13 B, +0.1%)
truapi/playground/codegen/truapi-dts.js 284.6 KiB (+39 B) 49.2 KiB (+20 B) 39.7 KiB (-21 B, -0.1%)

Commit: 55083f5

Review of #1319:
- The analyzer reports every code in a frame, so an unrelated code next to
  the product's cannot hide it. The QR-only constructor keeps reporting the
  first, as before.
- A prompt whose screen never appears closes it too, and a screen that
  appears after its prompt ended, or is restored in a new process, closes
  itself instead of crashing or answering nobody.
- Prompts close their own screen wherever it is in the back stack, and an
  answer no longer pops whatever sits on top.
- Camera results that arrive after the screen is gone are ignored.
- Any camera refusal shows the Settings dialog before answering.
- Imports in files this change only touches keep their original order.
- The README says the Android app draws the scanner.
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

CI Status: 24 required jobs green, 17 passed and 7 skipped by path filter.

All job results
job result
android-bindings skipped
bundle-size success
changes success
changeset-guard skipped
cli-package skipped
codegen success
e2e skipped
explorer success
headless-install success
host-android-bindings success
host-android-detekt success
host-wasm success
ios-bindings success
ios-swift skipped
licenses success
playground success
provider-android-bindings skipped
release-guard success
rust success
ts-client success
ts-debugger success
ts-host success
wasm-provider success
workflow-lint skipped

Signing credentials: failure as of 2026-10-09, a release may fail

Commit 55083f5d · run log

… top

Review of #1319:
- The scanner is a full-height sheet, not a screen. Opening a screen closed
  any sheet under it, including the product sheet whose product asked.
- Closing a prompt pops it only when it is on top, so a sheet over it stays.
  A prompt left under another sheet closes itself when it is shown again.
- Close and back always close the screen. Only the answer is given once.
- A one-time camera denial answers CameraUnavailable, since Android asks
  again next time. Only a permanent denial shows the Settings dialog.
- Any failure to show a prompt dismisses and closes it, and a screen that
  never appears fails the call instead of reporting that the user dismissed.
The reasons a prompt screen closes itself move to TrUAPIPrompts.current
instead of being repeated in each view model, and a stale test comment is
corrected.
@valentinfernandez1 valentinfernandez1 removed the rust Pull requests that update rust code label Oct 7, 2026
@valentinfernandez1
valentinfernandez1 added this pull request to stack #1311 October 7, 2026 19:20
@valentinfernandez1
valentinfernandez1 marked this pull request as ready for review October 8, 2026 18:45
@valentinfernandez1
valentinfernandez1 requested a review from a team October 8, 2026 18:45

@filvecchiato filvecchiato left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. One thing to check inline.

The product gets the answer before the closing sheet hands window focus back
to its page, so a scan made right away found the page off screen. An App's
check now waits up to a second for its page to get focus back.
@valentinfernandez1
valentinfernandez1 added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit ac4aed0 Oct 9, 2026
37 checks passed
@valentinfernandez1
valentinfernandez1 deleted the feat/host-scanner-android branch October 9, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation host-android Touches the Android host tree

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants