> For the complete documentation index, see [llms.txt](https://sandbox-docs.verifone.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sandbox-docs.verifone.com/home/psdk-sdk-peripherals/developer-guide/scanner.md).

# Scanner

Add the dependency (see Installation) and grant `android.permission.CAMERA`.

## Initialize

*Available since v0.3.0.*

```kotlin
val scanner = BarcodeScanner.create(context)
lifecycleScope.launch {
    if (!scanner.initialize()) {
        // scanner.status is UNAVAILABLE — not usable on this device right now
    }
}
```

Observe lifecycle state with `scanner.status` (`UNINITIALIZED`, `INITIALIZING`, `READY`, `SCANNING`, `UNAVAILABLE`).

## Scan (three styles)

*Available since v0.3.0.*

{% tabs %}
{% tab title="Full-screen" %}
Full-screen scanning is hosted for you — one call shows a built-in scanning screen and emits results, no layout or view management on your part. Pass a `FragmentActivity` as the host (from a `Fragment`, use `requireActivity()`), and collect on the main thread, since it drives a full-screen UI against the activity lifecycle:

```kotlin
// `this` is a FragmentActivity
scanner.scanFullScreen(this, ScanContinuity.SINGLE)
    .onEach { barcode -> println("${barcode.format}: ${barcode.data}") }
    .launchIn(lifecycleScope)
```

{% endtab %}

{% tab title="Headless" %}
No camera preview on screen — scan in the background and collect on any scope:

```kotlin
scanner.scanHeadless(ScanContinuity.CONTINUOUS)
    .onEach { barcode -> /* ... */ }
    .launchIn(scope)
```

{% endtab %}

{% tab title="Embedded preview" %}
Unlike full-screen, you place and manage the `ScannerView` in your own layout:

```xml
<com.verifone.psdk.peripherals.scanner.ScannerView
        android:id="@+id/scannerView"
        android:layout_width="match_parent"
        android:layout_height="240dp"/>
```

```kotlin
val view = findViewById<ScannerView>(R.id.scannerView)
view.attach(scanner, ScanContinuity.CONTINUOUS)
view.results().onEach { barcode -> /* ... */ }.launchIn(scope)
// later:
view.detach()
```

{% endtab %}
{% endtabs %}

### Continuity

* `SINGLE` — emits the barcode(s) of the first read, then the flow completes.
* `CONTINUOUS` — keeps emitting until you cancel collection; a code that stays in view or is re-presented is reported again after a short reread delay.

### Barcode result

Each decoded barcode is delivered as a `Barcode`:

* `data` — the decoded payload as text. On `ScannerBackend.HONEYWELL`, this is decoded as `ISO-8859-1`. On `ScannerBackend.ZXING`, this is the decoder's own best-effort text: it honors an ECI (Extended Channel Interpretation) designator when the symbol carries one, and otherwise guesses the encoding — so it is not always `ISO-8859-1`, and can differ from the `ScannerBackend.HONEYWELL` value for the same symbol (for example, a UTF-8 ECI-tagged QR code decodes as UTF-8 text on `ScannerBackend.ZXING` but as unreadable characters on `ScannerBackend.HONEYWELL`). See `rawBytes` for the payload's bytes, and how they can differ by backend and symbology.
* `rawBytes` — the decoded payload as raw bytes: the bytes the barcode carried, not the barcode's own encoding of them. Use this instead of `data` for symbologies that carry binary payloads, where decoding as text would be lossy. For a barcode that reports its payload only as text, this is `data` encoded as ISO-8859-1, or as UTF-8 where a character does not fit in a single byte — the difference consumers are most likely to meet: a kanji-mode QR code reports its Shift\_JIS bytes with `ScannerBackend.HONEYWELL` (12 bytes for six characters) and the same characters as UTF-8 with `ScannerBackend.ZXING` (18 bytes).
* `format` — the `BarcodeFormat` it was decoded from.

```kotlin
.onEach { barcode -> saveToFile(barcode.rawBytes) }
```

## Choose a scanner backend

*Available since v0.4.0.*

`ScannerConfig.backend` selects the decoding engine. It is read once, when the scanner is created:

```kotlin
val scanner = BarcodeScanner.create(
    context,
    ScannerConfig(backend = ScannerBackend.ZXING),
)
```

`ScannerBackend.HONEYWELL` is the default and decodes every symbology this library supports. `ScannerBackend.ZXING` cannot decode Code 11, MSI, Standard 2 of 5, Codablock-F, or GS1 DataBar Limited — those five are dropped from `ScannerConfig.symbologies` when `ZXING` is selected, and every remaining requested format is decoded as usual.

{% hint style="warning" %}
All five are part of `DEFAULT_SYMBOLOGIES`, so selecting `ZXING` with the default configuration decodes eleven of the sixteen default symbologies, not all sixteen.
{% endhint %}

Both backends support all three scan styles — headless, embedded preview, and full-screen — and deliver results as the same `Barcode` type, so the backend you pick does not change how you call the scanner.

The scanner never switches backend on its own: if the selected backend cannot be made ready, `initialize()` returns `false` and `status` becomes `UNAVAILABLE` rather than falling back to the other backend.

### Read the active backend

`BarcodeScanner.activeBackend` reports the backend the instance was created with:

```kotlin
val scanner = BarcodeScanner.create(
    context,
    ScannerConfig(backend = ScannerBackend.ZXING),
)
scanner.activeBackend // ScannerBackend.ZXING
```

It is read-only and fixed for the lifetime of the instance — an existing scanner cannot be re-pointed at the other backend. Read it when you want to log or display which engine a session is running on, for example while diagnosing a device-specific decode problem.

### Change backend at runtime

Because the backend is fixed per instance, switching means replacing the scanner: cancel any in-flight scan, close the current instance, then create and initialize a new one.

```kotlin
scanJob?.cancelAndJoin()
scanner.close()

scanner = BarcodeScanner.create(
    context,
    ScannerConfig(backend = ScannerBackend.ZXING),
)
scanner.initialize()
```

{% hint style="warning" %}
Wait for the in-flight scan to finish cancelling before calling `close()`. Closing while a scan is still tearing down lets the new instance start acquiring the camera before the old one has released it. The new instance still decodes either way — closing during a scan no longer leaves it streaming frames without decoding — so this is about a clean camera hand-off, not correctness.
{% endhint %}

### Per-format support

| `BarcodeFormat`                           | Honeywell | ZXing |
| ----------------------------------------- | :-------: | :---: |
| EAN8, EAN13, UPCA, UPCE                   |    Yes    |  Yes  |
| CODE39, CODE93, CODE128                   |    Yes    |  Yes  |
| GS1\_128                                  |    Yes    |  Yes  |
| CODABAR, ITF                              |    Yes    |  Yes  |
| QR, DATA\_MATRIX, PDF417, AZTEC, MAXICODE |    Yes    |  Yes  |
| DATABAR, DATABAR\_EXPANDED                |    Yes    |  Yes  |
| DATABAR\_LIMITED                          |    Yes    |   No  |
| CODE11                                    |    Yes    |   No  |
| MSI                                       |    Yes    |   No  |
| STANDARD\_2\_OF\_5                        |    Yes    |   No  |
| CODABLOCK\_F                              |    Yes    |   No  |

## Configure symbologies

*Available since v0.3.0.*

Supported formats: EAN-8/13, UPC-A/E, Code 39/93/128, GS1-128, Codabar, ITF (Interleaved 2 of 5), QR, Data Matrix, PDF417, Aztec, MaxiCode, GS1 DataBar (plus Limited and Expanded), Code 11, MSI, Standard 2 of 5 (a distinct symbology from ITF, despite the similar name), and Codablock-F. Only the formats you request are decoded. The backend you choose (see "Choose a scanner backend" above) constrains this further — `ScannerBackend.ZXING` cannot decode five of these symbologies (Code 11, MSI, Standard 2 of 5, Codablock-F, GS1 DataBar Limited).

{% hint style="warning" %}
All five are part of `DEFAULT_SYMBOLOGIES`. Selecting `ZXING` with the default configuration therefore decodes eleven of the sixteen default symbologies, not all sixteen.
{% endhint %}

```kotlin
val scanner = BarcodeScanner.create(
    context,
    ScannerConfig(symbologies = setOf(BarcodeFormat.QR, BarcodeFormat.EAN13)),
)
```

If `symbologies` is not set, `ScannerConfig` decodes `DEFAULT_SYMBOLOGIES`: EAN-8/13, UPC-A/E, Code 39/128, QR, Data Matrix, PDF417, GS1 DataBar (plus Limited and Expanded), Code 11, MSI, Standard 2 of 5, and Codablock-F. Aztec, Codabar, Code 93, GS1-128, ITF, and MaxiCode are not decoded unless requested explicitly:

```kotlin
import com.verifone.psdk.peripherals.scanner.model.DEFAULT_SYMBOLOGIES

// Decode the defaults plus Code 93
ScannerConfig(symbologies = DEFAULT_SYMBOLOGIES + BarcodeFormat.CODE93)
```

## Beep on scan

*Available since v0.3.0.*

A successful scan plays an audible beep by default. Set the initial state with `ScannerConfig.beepOnScan`, and change it at any time with `scanner.beepEnabled`:

```kotlin
val scanner = BarcodeScanner.create(context, ScannerConfig(beepOnScan = false)) // start silent
scanner.beepEnabled = true  // enable the beep later; takes effect immediately
```

The setting applies to every scan mode (embedded, full-screen, and headless).

## Reread delay

*Available since v0.3.0.*

In continuous scanning, the same barcode is reported again only after a reread delay while it stays in view or is re-presented (a different code is always reported immediately). Set the initial value with `ScannerConfig.rereadDelay` (a `kotlin.time.Duration`; default 2 seconds; `Duration.ZERO` reports every read), and change it at any time with `scanner.rereadDelay` — including on a scan that is already running:

```kotlin
import kotlin.time.Duration.Companion.milliseconds

val scanner = BarcodeScanner.create(context, ScannerConfig(rereadDelay = 500.milliseconds))
scanner.rereadDelay = 1000.milliseconds  // adjust later; takes effect immediately
```

The setting applies to every continuous scan mode (embedded, full-screen, and headless).

## Aimer button

*Available since v0.3.0.*

```kotlin
val aimer = AimerButtonControl(context)
aimer.setMode(AimerButtonMode.ENABLED)   // pressing the scan button lights the red aimer
aimer.setMode(AimerButtonMode.DISABLED)  // pressing it does not
aimer.setMode(AimerButtonMode.DEFAULT)   // device default

val current = aimer.mode  // read the current setting; DEFAULT when not overridden
```

Aimer-button control depends on platform support — VA/OS 6.14.1 or newer, 7.14.1 or newer, or 8.14.1 or newer. Where it's unsupported, `setMode` throws `ScannerException(ScannerError.AimerControlDenied)`. Treat the feature as optional and handle the rejection gracefully — everything else in the Scanner API works without it.

## Red light filter

*Available since v0.5.0.*

The red light the device shines on a barcode can flatten the contrast between bars and spaces enough to stop a code reading, even when it is in frame and in focus. `redLightFilter` suppresses that red in the image the scanner reads, which brings the contrast back:

```kotlin
val scanner = BarcodeScanner.create(
    context,
    ScannerConfig(redLightFilter = RedLightFilter.STRONG),
)
```

| Value      | Effect                                                                        |
| ---------- | ----------------------------------------------------------------------------- |
| `OFF`      | Default. The scanner reads the camera image unchanged.                        |
| `MODERATE` | Partial suppression, keeping more of the image's brightness.                  |
| `STRONG`   | Full suppression; the scanner reads only the part the red light cannot reach. |

The setting is fixed for a scanner — pass it to `create()` and it applies to every scan that scanner runs, on every scan style and both backends.

One limit is worth planning for:

* **It cannot recover a barcode the light has washed out completely.** Where the light saturates the camera, the bars are gone from the whole image and no filter brings them back. It helps the far more common case, where red glare lifts the image without erasing it.

Leave it `OFF` unless red glare is the problem you are solving. The filtered image is dimmer than the original, so in poor light *without* glare a filtered scan can read worse than an unfiltered one.

## Center window

*Available since v0.5.0.*

A terminal is often aimed at a surface holding more than one barcode — a shelf edge, a loyalty card carrying several codes, a form, a shipping label. `centerWindow` restricts reading to a region around the center of the camera image, so an app can choose which barcode it wants: a barcode outside the region is not reported.

```kotlin
val scanner = BarcodeScanner.create(
    context,
    ScannerConfig(centerWindow = CenterWindow.MEDIUM),
)
```

| Value    | Region                    | Largest barcode it still reads | Closest competing barcode it excludes  |
| -------- | ------------------------- | ------------------------------ | -------------------------------------- |
| `OFF`    | Default. The whole image. | Any                            | None — every barcode found is eligible |
| `WIDE`   | Middle 80% of each axis   | About 80–90% of the width      | About 15% of the width away            |
| `MEDIUM` | Middle 50% of each axis   | About 50–60% of the width      | About 10% of the width away            |
| `NARROW` | Middle 20% of each axis   | Under about 30% of the width   | About 2% of the width away             |

Sizes in that table are fractions of the image width, and a barcode's width counts the clear space it needs on either side, so a barcode can measure slightly wider than the region and still read. Note too that the region is a fraction of *each axis*, so `MEDIUM` covers a quarter of the image area, and `NARROW` about a twenty-fifth.

The figures in that table were established with `ScannerBackend.ZXING`. `ScannerBackend.HONEYWELL` — the default — applies the same region but judges containment itself, so for that backend these figures are indicative rather than exact.

The setting is fixed for a scanner — pass it to `create()` and it applies to every scan that scanner runs.

Read the last two columns together, because they are one trade-off: a tighter region excludes a competing barcode that sits closer to the one you want, but it also reads only smaller barcodes. Choose from how large the barcode is in the image rather than from how precisely the operator aims. In practice the two go together — when several barcodes are in view at once, each one is small.

Three limits are worth planning for:

* **A barcode wider than the region cannot be read at all.** `NARROW` reads nothing when the barcode fills much of the image, however well it is centered.
* **A competing barcode too close to the wanted one gives an unreliable result.** The table's closest competing-barcode figure for each preset marks the distance below which the result becomes unreliable: closer than that, the scanner may report the competing barcode instead of the wanted one, or report nothing at all. Leave room between them, or choose a region whose edge falls clear of the neighbor.
* **Nothing is ranked.** A barcode outside the region is never reported, even when it is the only one in view, so an operator aiming away from the center gets no read rather than the wrong read. The library draws no on-screen guide, so an app that turns this on should show the operator where to aim.

`WIDE` excludes only what is near the image edges. It suits trimming away the margins, not choosing between two barcodes on the same label.

## Keep the camera ready

*Available since v0.5.0.*

`keepCameraWarm` keeps the camera open between scans, so a scan does not have to open it from scratch:

```kotlin
val scanner = BarcodeScanner.create(
    context,
    ScannerConfig(keepCameraWarm = true),
)
```

Off by default. When it is on, the scanner opens the camera as soon as it is prepared and keeps it ready until the scanner is closed. Every scan re-establishes that readiness first, so readiness that was lost is restored rather than given up on. Applies to every scan style on both backends.

Scanning is noticeably faster with the option on than with it off: an already-open camera removes most of the time a scan would otherwise spend getting the camera ready to deliver frames, though not quite all of it — plan for scans that start quickly, not for scans that begin instantly. The trade is that preparing the scanner takes longer, because that is when the camera is opened instead. How much either changes depends on the terminal and on how recently the camera was used — expect both to vary rather than a fixed amount either way.

Turning it on comes with costs an integrator has to accept deliberately:

* **The camera is held ready for as long as the scanner is open, so it is not normally available to other applications.**
* **The host application's own camera use is affected too.** While the scanner holds the camera ready, the host application's own camera work is suspended — this is not only about other applications, and is the cost most likely to be missed.
* **The device draws more power while this is on**, since the camera stays open and actively running between scans rather than only while a scan is in progress — weigh this against the speed benefit on battery-powered terminals.

It does not change the aimer illumination — that is driven by the physical scan button (see [Aimer button](#aimer-button)), independently of the camera.

It recovers between scans, on its own. Another application can still take the camera away from a scanner holding it ready; when that happens, or when the host application uses the camera itself, the next scan opens the camera exactly as it would with the option off, and readiness resumes from there — the option does not turn itself off for good. A camera taken while a scan is already running recovers on its own too, the same way it does with the option off, whether the scan is headless or has a preview showing — reporting resumes, or the preview lights back up, without the scan having to be started again. The two do not recover at the same pace, though: recovery on the preview path can take long enough that it may look like the scan will not recover at all, even though it eventually does — plan for that path to look worse before it looks better, and less predictably than a headless scan. Closing the scanner gives the camera back.

Best-effort: if the camera cannot be opened when the scanner is prepared — the camera permission has not been granted yet, or another application is using it — the scanner still becomes ready and scans as it does with the option off. Each scan tries again, so while the camera stays unavailable a scan spends a little time on that attempt before it starts.

Whether a scanner keeps a camera ready is decided by its own `keepCameraWarm` setting, not by which scanner in an application was initialized most recently — a scanner with the option off is unaffected by another scanner that has it on. One scanner scanning at a time remains the supported arrangement, since two scanners that both keep a camera ready can still interfere with each other.

## Scan speed mode

*Available since v0.6.0.*

`scanSpeedMode` trades barcode-decode speed against read range:

```kotlin
val scanner = BarcodeScanner.create(
    context,
    ScannerConfig(scanSpeedMode = ScanSpeedMode.FAST),
)
```

| Value           | Effect                                         |
| --------------- | ---------------------------------------------- |
| `BALANCED`      | Default. Balances decode speed and read range. |
| `FAST`          | Favors decode speed over read range.           |
| `HIGH_ACCURACY` | Favors read range over decode speed.           |

The setting is fixed for a scanner — pass it to `create()` and it applies to every scan that scanner runs, on every scan style and both backends.

How large the effect is — and whether a given mode changes anything at all — can vary by backend and by device. Try `FAST` or `HIGH_ACCURACY` against the terminal and barcodes an app actually uses before relying on either in production, rather than assuming a fixed improvement.

## Handling errors

*Available since v0.3.0.*

Recoverable failures are reported as a `ScannerException` carrying a `ScannerError`:

* `NotReady` — a scan was started before `initialize()` reached `READY`.
* `CameraUnavailable` — the camera could not be opened for a scan.
* `AimerControlDenied` — the aimer-button setting cannot be changed on this device.
* `Unexpected` — any other failure; its `message` is a diagnostic string and `cause` is the original error.

A scan is delivered as a flow, so a failure surfaces by terminating that flow rather than throwing from the call that starts it. Collect with `catch` to handle it. The flow always fails with a `ScannerException` — never a raw platform error:

```kotlin
scanner.scanHeadless(ScanContinuity.CONTINUOUS)
    .onEach { barcode -> /* ... */ }
    .catch { e ->
        val error = (e as? ScannerException)?.error
        // handle NotReady / CameraUnavailable / Unexpected
    }
    .launchIn(scope)
```

The same applies to `scanFullScreen(...)` and `ScannerView.results()`.

`initialize()` returns `false` when the scanner is simply unavailable on this device; it throws `ScannerException(ScannerError.Unexpected)` only if preparation fails unexpectedly. It's safe to call `initialize()` again after a failed attempt.

## Diagnosing scan performance

*Available since v0.5.0.*

The library reports how long each scan took to the Android log, so a slow scan can be attributed rather than guessed at. Capture it with:

```shell
adb logcat -s PeripheralsScanner
```

Each scan session produces two lines: one for the first barcode it reports, and a summary when the session ends.

```
scan first-hit headless/ZXING total=1180ms gate=4ms camOpen=412ms toFirstHit=764ms frames=12 decodeAvg=58ms fmt=QR len=94
scan session-end headless/ZXING scans=7 elapsed=14200ms interval min=2001ms avg=2010ms max=2043ms frames=118 decodeAvg=61ms
```

| Field          | Meaning                                                                                                                                                                                                                                                                                                                                                                        |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `redLight`     | Which `RedLightFilter` was configured, appended right after the mode/backend as `/redLight=<STRENGTH>`. Reported on the first-hit and session-end lines, not the per-scan `#N` lines. Absent when the setting is `OFF` (the default).                                                                                                                                          |
| `window`       | Which `CenterWindow` preset was configured, appended right after the mode/backend — and after `redLight`, when both are present — as `/window=<PRESET>`. Reported on the first-hit and session-end lines, not the per-scan `#N` lines. Absent when the setting is `OFF` (the default).                                                                                         |
| `warm`         | Appended as `/warm` right after `window` — and after `redLight`, when both are present — when this session began on a camera that was already open. Reported on the first-hit and session-end lines, not the per-scan `#N` lines. Absent when the session had to open the camera itself, even with `keepCameraWarm` on — it marks what happened, not what was configured.      |
| `total`        | The whole span, from starting the scan to reporting the first barcode. Approximately the sum of the phases below — each phase is truncated to whole milliseconds, so the sum can land a millisecond or two short of the total.                                                                                                                                                 |
| `screenShow`   | How long it took to build the library's full-screen scanning screen, measured from the call that asked for the scan. Reported for the full-screen style only — the other two styles scan in a view you already have. The screen finishes sizing and drawing after this point, so that remainder falls into the phases below rather than here.                                  |
| `gate`         | Time spent waiting for a previous scan to release the camera. Seconds here means a scan was switched while the last one was still stopping.                                                                                                                                                                                                                                    |
| `camOpen`      | How long the camera took to deliver its first image. Reported for every scan style. For the embedded and full-screen styles this is measured from `previewStart`, not from `gate` directly, so the two still add up to `total` alongside the other phases.                                                                                                                     |
| `previewStart` | How long the preview took to start scanning. Reported for the embedded and full-screen styles.                                                                                                                                                                                                                                                                                 |
| `toFirstHit`   | Time spent looking for a barcode once the camera was ready. **This includes the operator pointing the terminal at the barcode**, so it is normally the largest number and normally not a fault.                                                                                                                                                                                |
| `frames`       | Images the camera delivered. Reported for scans without a preview. Only the freshest image is examined at any moment, so this is higher than the number actually decoded.                                                                                                                                                                                                      |
| `decodeAvg`    | Mean time to examine one image, averaged over the images actually examined. Reported for every scan style.                                                                                                                                                                                                                                                                     |
| `sincePrev`    | Gap since the previously reported barcode. In continuous scanning this cannot be shorter than the [reread delay](#reread-delay), so a barcode held in view repeats at that interval. The line's `#N` counts the barcode's position in the session — the first hit is logged as `first-hit` rather than `#1`, so this line begins at `#2`.                                      |
| `fmt`, `len`   | The symbology, and the size of the barcode's payload in bytes. Payloads themselves are never logged.                                                                                                                                                                                                                                                                           |
| `scans`        | Barcodes reported over the whole session, including the first hit.                                                                                                                                                                                                                                                                                                             |
| `elapsed`      | Time from the start of the session — the same starting point `total` is measured from — to when the session ended.                                                                                                                                                                                                                                                             |
| `min`          | Part of the session-end line's `interval` group: the shortest gap between two consecutively reported barcodes in the session. Present only once the session has reported at least two barcodes.                                                                                                                                                                                |
| `avg`          | Part of the same `interval` group: the mean gap between consecutively reported barcodes — `(last report − first report) / (reports − 1)`. **Not an average scan latency.** In continuous scanning it normally sits close to the [reread delay](#reread-delay), since that is what paces a code held in view. Present only once the session has reported at least two barcodes. |
| `max`          | Part of the same `interval` group: the longest gap between two consecutively reported barcodes in the session. Present only once the session has reported at least two barcodes.                                                                                                                                                                                               |

A line is printed only for the phases that apply to the scanning style in use; a phase the library cannot observe is left out rather than reported as `0ms`. The phases reported for a given scan style are the same on both backends.

`initialize()` reports its own duration, split so a slow start can be attributed to licensing rather than to preparing the decoder and the symbologies it will look for:

```
Scanner initialized (READY) in 412ms (activation=380ms, decoder=32ms)
```

With `keepCameraWarm` on, opening the camera happens during preparation too, and the line gains a `camera` term for how long that took — present whenever the option is on, even if the camera could not be opened, and absent otherwise:

```
Scanner initialized (READY) in 512ms (activation=380ms, decoder=32ms, camera=100ms)
```

If the scanner turns out to be unavailable instead, the same duration is reported under the warning that announces it, as `(after Nms)` — how long entitlement activation ran before it gave up:

```
Scanner UNAVAILABLE — entitlement not activated (after 380ms)
```

### Releasing a scanner

Releasing a scanner reports its own line. This is the other half of a long `gate`: that field says a scan waited for the previous one to let go of the camera, and this line says what it was waiting for.

```
scanner teardown HONEYWELL total=3840ms deferred=1250ms dispose=2590ms
```

| Field      | Meaning                                                                                                                                                                                        |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `total`    | The whole release, from the call that closed the scanner to the point its resources were free.                                                                                                 |
| `deferred` | Of that, time spent waiting for a scan that was still running to stop, before releasing could begin. `0ms` when nothing was running. This is the same wait a following scan reports as `gate`. |
| `dispose`  | The release itself, once nothing was using the camera or the decoder any more.                                                                                                                 |

Closing a scanner returns immediately, so this line appears when the release actually finishes — which, if a scan was still running, is after the call that asked for it.

With `ScannerBackend.HONEYWELL`, the decoder is shared between every scanner in the process, so releasing the last one also discards the entitlement activation that licensed it. That is reported separately, and it is what explains an `initialize()` that spends time on activation when the one before it did not:

```
Activation forgotten; shared decoder teardown took 2580ms — the next initialize() re-activates
```

### More detail

Every barcode after the first is logged at debug level, which Android suppresses by default. Enable it when you need per-barcode timings — continuous scanning can report several barcodes a second, which is why it is not on by default:

```shell
adb shell setprop log.tag.PeripheralsScanner DEBUG
```

The setting lasts until the device restarts.

## Release

*Available since v0.3.0.*

```kotlin
scanner.close()
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://sandbox-docs.verifone.com/home/psdk-sdk-peripherals/developer-guide/scanner.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
