# QR Scanner
The QR Scanner provides a way of scanning QR codes and bar codes directly from
Chrome. It is developed behind the `EnableQRCodeReader` experimental flag.
[TOC]
## Usage
1. Create a delegate implementing the `QRScannerViewControllerDelegate`
protocol.
2. Initialize `QRScannerViewController` with this delegate.
3. Present the view controller returned by `viewControllerToPresent`.
## Behavior
The QR Scanner is presented as a full-screen view controller displaying a video
preview, a control for the camera's torch, and a control for closing the QR
scanner.
### Presentation
The QR Scanner is presented using a custom transition animation which makes it
appear to be originally positioned under the presenting view controller.
### Scanning
* Codes are only recognized inside the viewport.
* A flash animation is played when a code is recognized.
* If VoiceOver is enabled, an announcement is played instead of the animation.
* Scanning a QR code or any other code type which can contain alphanumeric
strings places the scanned result in the Omnibox, and the user has to press
the "Go" button on the keyboard to load the result. Non-URL strings will be
loaded in search.
* Scanning a bar code type which can only contain numbers will load search
results containing the bar code immediately without waiting for user
confirmation.
### Torch
* Torch is switched off every time the QR scanner is opened, closed, or the
camera session is interrupted.
* The torch button always reflects the current torch state.
* The torch button is in a disabled state if the camera does not have torch,
the torch is unavailable, or the camera is not yet loaded.
### Errors
* A dialog is displayed if the camera is unavailable, camera permissions are
not granted, the camera is in use by another application, or the application
is in Split View on iPad.
* Pressing the "Cancel" button of any error dialog dismisses the QR Scanner
view controller.
* If the camera becomes available when a dialog is presented, the dialog is
automatically dismissed.
## Entry points
The QR scanner can be accessed from the 3D Touch application shortcuts on
supported devices. The `SpotlightActions` experiment allows the QR scanner to be
accessed from Spotlight search. More info about Spotlight actions can be found
at go/chrome-ios-spotlight and crbug.com/608733. Planned and rejected entry
points are described in the design doc at go/chrome-ios-qr-code.
Tests for QR Scanner are a part of `ios_chrome_ui_egtests`.
## Controller architecture
* **QRScannerViewController** is the entry point for the feature. It connects
the `CameraController` and the `QRScannerView` and is responsible for
displaying alerts from `QRScannerAlerts`.
* **CameraController** manages the `AVCaptureSession` for the camera. It is
responsible for loading the camera, listening for camera notifications,
receiving the scanned result and informing the `QRScannerViewController`
about changes to the state of the camera or the torch.
Operations performed by `CameraController` are done on a separate dispatch
queue, as recommended by the [documentation][avcapturesession] for
`AVCaptureSession`.
[avcapturesession]: https://developer.apple.com/reference/avfoundation/avcapturesession
### Initialization
`QRScannerViewController` owns an instance of `CameraController` and
`QRScannerView` and is their delegate.
1. The `viewControllerToPresent` method checks if camera permission is
granted by calling `checkPermissionsAndLoadCamera` of the
`CameraController`.
2. If the camera permission is denied, an error dialog will be returned from
`viewControllerToPresent`. If the user has not previously granted camera
permission to the application, the `QRScannerViewController` instance will
be returned, and an error will be displayed by the QR scanner if the user
denies the permission in the system dialog. The error dialog prompts the
user to change this setting and includes a link to the Settings app, if
available.
3. If the camera permission is granted, the `QRScannerViewController` will be
returned as the view controller to present, and the `CameraController` will
start loading the camera on a separate dispatch queue.
#### Camera
Camera initialization is handled by the `loadCaptureSession` method of the
`CameraController`.
1. Camera state is set to `CAMERA_UNAVAILABLE` and an error dialog is
displayed if:
* The back camera of the device is not found,
* There was an error initializing the camera input,
* The video input cannot be attached to the `AVCaptureSession`,
* The metadata output cannot be attached to the `AVCaptureSession`,
* The metadata output does not support QR code recognition.
2. After a successful initialization, camera state is set to
`CAMERA_AVAILABLE`, which is reported asynchronously to
`QRScannerViewController`, and `CameraController` starts listening for
camera notifications.
3. Camera starts recording on `viewWillAppear`.
#### Torch
* Torch availability is checked when the camera initialization completes.
* Torch is considered available, if the properties `hasTorch` and
`isTorchAvailable` of the `AVCaptureDevice` are both `YES`.
* During initialization, it is also checked if the torch supports the torch
modes `AVCaptureTorchModeOn` and `AVCaptureTorchModeOff`.
* Torch mode is set to off on initialization.
#### Camera preview
The `AVCaptureVideoPreviewLayer` is created by the `QRScannerView`:
1. The `QRScannerView` is initialized by the `QRScannerViewController`.
2. On `viewDidLoad`, `QRScannerViewController` calls `loadVideoPreviewLayer:`
with the loaded preview. If the camera is already loaded,
`CameraController` attaches the preview to the `AVCaptureSession`. Otherwise
the preview is attached immediately after the `AVCaptureSession` is
initialized.
#### Viewport
The rectangle of interest for the metadata output of the capture session is
calculated to lie exactly inside the viewport drawn by the `QRScannerView`.
Resetting the viewport causes the video preview to freeze for a short while,
that is why the viewport is only set when the preview is hidden.
1. `QRScannerViewController` sets the viewport on `viewDidAppear`, to make sure
that the preview layer is of the correct size and position when the viewport
is calculated.
2. If the capture session is loaded, the viewport is set immediately. Otherwise
the viewport is set after the capture session is loaded.
3. `CameraController` calls the `cameraIsReady` method of its delegate to
notify the `QRScannerViewController` that the viewport was successfully set
and the camera preview can be displayed.
### Camera state and notifications
`CameraController` is listening for the following notifications:
1. `AVCaptureSessionRuntimeErrorNotification`, handled by setting the camera
state to `CAMERA_UNAVAILABLE`,
2. `AVCaptureSessionWasInterruptedNotification`, handled by setting the camera
state to one of:
* `APPLICATION_IN_BACKGROUND`,
* `CAMERA_IN_USE_BY_ANOTHER_APPLICATION`,
* `MULTIPLE_FOREGROUND_APPS`,
based on the value of `AVCaptureSessionInterruptionReasonKey` in the
notification's user info.
3. `AVCaptureSessionInterruptionEndedNotification`, handled by setting the
camera state to `CAMERA_AVAILABLE`.
4. `AVCaptureDeviceWasDisconnected`, handled by setting the camera state to
`CAMERA_UNAVAILABLE`.
### Torch state
The current state of the torch is obtained using key-value observing of the
`AVCaptureDevice` object.
* Torch state is set based on the value of the `torchActive` property, and
the delegate is informed using `torchStateChanged:`.
* Torch availability is set based on the values of `hasTorch` and
`torchAvailable`. Torch is only considered available if both properties are
`YES` and the delegate is informed using `torchAvailabilityChanged:`.
The delegate sets the value of the torch using `setTorchMode:` and is informed
of the outcome asynchronously.
### Scanning
`CameraController` implements the `AVCaptureMetadataOutputObjectsDelegate` and
receives the scanned result on the main queue.
* The scanned result must be an `AVMetadataMachineReadableCodeObject`.
* Only results which are non-empty strings are passed on to the
`QRScannerViewController`.
* `CameraController` checks the type of the scanned code, and if the code can
only contain numbers, sets the `loadImmediately` argument to `YES`.
* If a valid code was scanned, the `CameraController` stops the capture
session.
Supported codes (from [Machine Readable Object Types][machinereadableobjects]):
* Numeric-only bar codes: UPC-E, EAN-8, EAN-13, Interleaved 2 of 5, ITF-14
* Alphanumeric bar codes: Code 39, Code 39 Mod 43, Code 93, Code 128
* 2D alphanumeric codes: PDF417, AztecCode, DataMatrix, QR Code
[machinereadableobjects]: https://developer.apple.com/reference/avfoundation/avmetadatamachinereadablecodeobject/1668878-machine_readable_object_types?language=objc
## Views
The QR scanner consists of three views:
* **VideoPreviewView** holds the camera preview.
* **PreviewOverlayView** holds layers drawing the darker preview overlay and
the viewport border.
* **QRScannerView** contains the `VideoPreviewView`, `PreviewOverlayView` and
controls as subviews.
### Transition animation
`QRScannerTransitioningDelegate` implements a custom transition animation:
the `QRScannerViewController` appears to be positioned below its presenting view
controller. The presenting view controller slides up for presentation and down
for dismissal.
### Screen rotation
* `QRScannerView` and `PreviewOverlayView` rotate normally.
`VideoPreviewView` does not rotate: on `viewWillTransitionToSize:` the view
is animated to rotate in the opposite direction. This avoids resetting the
viewport rectangle of interest on every screen rotation, because resetting
it causes the video to pause for a while. The view is not positioned using
AutoLayout.
* `PreviewOverlayView` is a square that is `sqrt(2)`-times bigger than
max(width, height) of the `QRScannerView`, to avoid redrawing the
viewport. This also makes the viewport rotate in place. The view is
positioned using AutoLayout.
### Split View
* Camera is unavailable in Split View.
* When the camera becomes available, the viewport rectangle is reset,
otherwise the viewport would be in the wrong place when Split View is
cancelled.
## Accessibility
* Standard UI elements have accessibility labels and identifiers.
* The value of the torch button is communicated using its accessibility value.
* If VoiceOver is on, an accessibility announcement is played when a code is
scanned, and the result is loaded after the announcement finishes.
* If the result is loaded immediately, no additional accessibility notifications are
posted.
* If the result is placed in Omnibox for the user to review, the Omnibox
should be focused afterwards.
## Metrics
The following metrics are collected:
* `IOS.Spotlight.Action` when the user opens the QR scanner from searching for
it in Spotlight.
* `ApplicationShortcut.ScanQRCodePressed` when the user opens the QR scanner
from 3D Touch application shortcuts.
* `MobileQRScannerClose` when the user closes the QR scanner without scanning
a code.
* `MobileQRScannerError` when the user closes the QR scanner from an error
dialog.
* `MobileQRScannerScannedCode` when the user scans a code.
* `MobileQRScannerTorchOn` when the user switches on the torch.
## Known issues
Screen rotation on iPad is not handled the same way as on iPhone, as of iOS 9.
The counter-rotation animation is not played at the same time as the screen
rotation animation, and the camera preview appears to be rotating. This effect
is most visible when rotating the screen by 180 degrees, which results in an
apparent double-rotation by 360 degrees.
## See also
* go/chrome-ios-qr-code for the original design doc.