This pattern is useful for continuously running Android components while shifting functionality and not interrupting the UI.

Open Source Code:

With Android Lollipop’s Camera2 API developers were given the opportunity to customize the camera experience in their apps. Some of the features outlined by Developer Advocate David East at Google Developers in his post Detecting camera features with Camera2, include auto-focus, exposure, sensor controls, effects, filters, interval control, and so on... I tinkered with the camera’s barcode detection and scanning capabilities in order to create the ScannerApp sample showcasing smooth transitioning between barcode scanning modes while keeping the camera running. This results in seamless toggling between barcode types, which is a useful feature for switching between any type of camera detection.

Implementation

Architecture

highly technical drawing by yours truly ;-)

This pattern is useful for continuously running Android components (like the camera) while smoothly shifting functionality (barcode types) and not interrupting the UI.

ScannerApp is designed to do the heavy lifting in one central location, the BarcodeActivity contains the camera logic including returned barcode results. The results (represented in green above) flow down to the BarcodeFragment → ViewPager. The ViewPager knows which Fragment is active and passes the results accordingly. Finally, the results are displayed in the BarcodeFragmentOverlay. This architecture keeps the camera logic in the BarcodeActivity de-coupled from the UI logic in the BarcodeFragmentOverlay.

Building Upon Google Vision and Camera2Vision Samples

Google Vision

When creating the BarcodeTrackerFactory in the BarcodeActivity I added Context to pass in as a parameter. The factory class creates the BarcodeGraphicTracker that consumes the barcode results in the overrided method onNewItem(). In order to share this data with my Activity I created an interface UpdateBarcodeListener that uses the context to ensure the Interface is implemented within the Activity. In onNewItem() I use the interface method getBarcode() to pass barcodes up to the Activity level.

Note: the getBarcode() method in the interface needs to run on the UIThread.

Activity

runOnUiThread {

(supportFragmentManager.findFragmentById(R.id.content) as

BarcodeFragment).addBarcode(barcode)

}

BarcodeTrackerFactory

BarcodeGraphicTracker

Camera2Vision

I used Ezequiel Adrian’s sample for his Camera2Source which manages the camera lifecycle. When the camera source is created it passes in a BarcodeDetector that communicates to the camera source the scanner type(s) to identify. In the current state, the only way to update the camera’s detector type is to pause/stop the camera and restart/recreate it.

By turning the Detector into a private instance variable a refreshDetector() method can be used to update the camera source’s detector while keeping the camera lifecycle active.

public void refreshDetector(Detector<?> detector){

mDetector = detector;

}

On the Activity level a new Detector can be created after the existing one is released (clearing the UI of the old barcode graphics), and then the refreshDetector() method can be called.

Activity

override fun updateScannerType(scannerType: Int) {

barcodeDetector.release()

createBarCodeDetector(scannerType)

camera2Source.refreshDetector(barcodeDetector)

} ... private fun createBarCodeDetector(scannerType: Int) {



barcodeDetector = BarcodeDetector.Builder(this)

.setBarcodeFormats(scannerType)

.build()



if (barcodeDetector.isOperational()) {

barcodeDetector

.setProcessor(

MultiProcessor.Builder(BarcodeTrackerFactory(

graphicOverlay, this))

.build())

} else {

Toast.makeText(this, "BARCODE DETECTION NOT AVAILABLE",

Toast.LENGTH_SHORT).show()

}



}

Resources

Documentation

Samples