Barcode Scanning API Programmer's Guide

Overview

The aim of this guide is to familiarize the user with the Barcode Scanner SDK for Android and its functionalities. Instructions are given in detail about integrating the Barcode Scanner SDK to Android Studio Project and to work with APIs for users to develop their applications. The provided code snippets can benefit the user in implementing LED, Beeper, Firmware Updates etc. Each functionality is explained individually with screenshots where necessary for the easy understanding of the user and to make development with reduced time and effort.


Configuring Development Environment

Adding the Barcode Scanner SDK to the Android Studio Project

Add the barcode_scanner_library_vx.x.x.x (ex: barcode_scanner_library_v2.6.13.0) into libs directory in the app module.

Figure 1 Add Barcode Scanner SDK

Navigate to File → Project Structure → Dependencies. In the dependencies tab, click the "+"" icon and select Jar Dependency in the dropdown.

Figure 2 Project Dependencies

In the Add Jar/Aar Dependency dialog, enter the path for the barcode_scanner_library_vx.x.x.x.aar file and select the "implementation" configuration. Click “ok” to proceed.

Figure 3 Add Jar/Aar Dependency

Add below Androidx lifecycle dependency to the app level build.gradle(:app) file.


implementation "androidx.lifecycle:lifecycle-process:2.5.1"

Adding Permissions in the Manifest File

Following permissions should be added in the Manifest File in order for the app to establish a Bluetooth connection with the scanner.


<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" tools:targetApi="s" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" tools:ignore="CoarseFineLocation" />

The BLUETOOTH_ADVERTISE, BLUETOOTH_CONNECT, and BLUETOOTH_SCAN permissions are runtime permissions. Therefore user approval must be explicitly requested in the app.

Connecting the Scanner to the Device

User can make a connection between the scanner and the device using either 'SSI BT Classic (discoverable)' Barcode or 'Scan-To-Connect' Barcode.

Using the 'SSI BT Classic (discoverable)' barcode (This is available in the Program Reference Guide of the Zebra Scanner Product), a Bluetooth connection can be established with the scanner and the device on which the app is running. Scan the barcode and start a Bluetooth scan in your device. Once the scanner is detected, pair with it.

SST BT Classic (Discoverable)

The noteworthy difference between the two barcodes is that, Scan-To-Connect doesn't require setting up the connection manually with the Android device. Unlike 'SSI BT Classic (discoverable)', a one scan of the Scan-To-Connect barcode establishes a connection with both the device and the app in one go.

SDK Installation

Inside the 'onCreate' method SDK must be initialized. Out of the several available operational modes (such as DCSSDK_MODE.DCSSDK_OPMODE_SNAPI, DCSSDK_MODE.DCSSDK_OPMODE_BTLE), Operational Mode of the Scanner must be set to DCSSDK_OPMODE_BT_NORMAL. Even though other operational modes are present, this guide is mainly focused on making a connection via DCSSDK_OPMODE_BT_NORMAL and carrying out the functions. Along with the initialization of the SDK an ArrayList to contain scanner information must be created.


public static SDKHandler sdkHandler;
private ArrayList scannerInfoList = new ArrayList();
int scannerId;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    sdkHandler = new SDKHandler(this);
    DCSSDKDefs.DCSSDK_RESULT result = sdkHandler.dcssdkSetOperationalMode(DCSSDK_OPMODE_BT_NORMAL);
}

Establishing a Connection with the Scanner

For the ease of explaining, two buttons have been used in the UI to establish the connection with the connected scanner and to make the scanner beep. With the press of the button used for connecting, the following 'connectToScanner' method gets called. If the 'sdkHandler' is not null, 'scannerInfoList' will get filled with all the Scanners that are connected to the device. Here inside the try catch block the 'scanner ID' of the first connected scanner in the 'scannerInfoList' is taken and then 'connectScanner' AsyncTask gets executed. Here an AsyncTask is used to prevent the main thread from being blocked while the scanner connection task is taking place.

Figure 4 Demo Application


public void connectToScanner(View view){
    if (sdkHandler != null) {
        sdkHandler.dcssdkGetAvailableScannersList(scannerInfoList);
    }

    try{
        scannerId = scannerInfoList.get(0).getScannerID();
        new connectScanner(scannerId).execute();
    }catch (Exception e){
        Toast.makeText(getApplicationContext(),e.toString(),Toast.LENGTH_SHORT).show();
    }
}

With 'dcssdkEstablishCommunicationSession' the app makes a connection with the Scanner.


private class ConnectScanner extends AsyncTask {
    int scannerId;
    
    public ConnectScanner(int scannerId){
        this.scannerId=scannerId;
    }

    @Override
    protected Void doInBackground(Void... voids) {
        sdkHandler.dcssdkEstablishCommunicationSession(scannerId);
        return null;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }
}

Making the Scanner Beep


public void beeperAction(View view) {
    String inXml = "<inArgs><scannerID>" + scannerId+ "<scannerID><cmdArgs><arg-int>"+ 1 +"<arg-int><cmdArgs><inArgs>";
    StringBuilder outXml = new StringBuilder();

    new BeepScanner(scannerId, DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_SET_ACTION,outXml).execute(new String[]{inXml});
}

Here the 'BeepScanner' AsyncTask gets called and then the 'executeCommand' method gets executed. One has the option to replace 1 in the 'inXml' with any integer between 0 and 26 as there are 27 beep combinations available.

Given below is a code snippet detailing the 27 combinations available for the scanner.


<resources>
    <string name="app_name">Barcode Scanner SDK - Android</string>

    <string-array name="beeper_actions">
        <item>One high short beep
        <item>Two high short beeps
        <item>Three high short beeps
        <item>Four high short beeps
        <item>Five high short beeps
        <item>One low short beep
        <item>Two low short beeps
        <item>Three low short beeps
        <item>Four low short beeps
        <item>Five low short beeps
        <item>One high long beep
        <item>Two high long beeps
        <item>Three high long beeps
        <item>Four high long beeps
        <item>Five high long beeps
        <item>One low long beep
        <item>Two low long beeps
        <item>Three low long beeps
        <item>Four low long beeps
        <item>Five low long beeps
        <item>Fast warble beep
        <item>Slow warble beep
        <item>High-low beep
        <item>Low-high beep
        <item>High-low-high beep
        <item>Low-high-low beep
        <item>High-high-low-low beep
    </string-array>
<resources>

Inside the 'BeepScanner' AsyncTask, parameters such as 'scannerId', 'opcode', 'outXml' get passed on to the 'executeCommand' method which finally gives out the corresponding beep.


private class BeepScanner extends AsyncTask<String,Integer,Boolean> {
    int scannerId;
    DCSSDKDefs.DCSSDK_COMMAND_OPCODE opcode;
    StringBuilder outXml;

    public BeepScanner(int scannerId, DCSSDKDefs.DCSSDK_COMMAND_OPCODE opcode, StringBuilder outXml){
        this.scannerId=scannerId;
        this.opcode=opcode;
        this.outXml = outXml;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected Boolean doInBackground(String... strings) {
        return  executeCommand(opcode,strings[0],outXml,scannerId);
    }

    @Override
    protected void onPostExecute(Boolean b) {
        super.onPostExecute(b);
    }
}


public boolean executeCommand(DCSSDKDefs.DCSSDK_COMMAND_OPCODE opCode, String inXml, StringBuilder outXml, int scannerId) {
    if (sdkHandler != null) {
        if(outXml == null) {
            outXml = new StringBuilder();
        }

        DCSSDKDefs.DCSSDK_RESULT result=sdkHandler.dcssdkExecuteCommandOpCodeInXMLForScanner(opCode,inXml,outXml,scannerId);

        if(result== DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_SUCCESS) {
            return true;
        }
        else if(result== DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_FAILURE) {
            return false;
        }
    }
    return false;
}

LED On/Off

The following UI is used to explain the LED ON/OFF states of the scanner. Code snippets for establishing a Bluetooth connection with the scanner are given above.

Figure 5 Demo Application

The method 'redLedOnClicked' gets called with the press of the 'LED ON' button. Similarly, 'redLedOffClicked' gets called with the press of the button 'LED OFF'.


public void redLedOnClicked(View view) {
    inXml = prepareInXml(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_RED_ON);
    performLedAction(inXml);
}


public void redLedOffClicked(View view) {
    inXml = prepareInXml(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_RED_OFF);
    performLedAction(inXml);
}

Using the following code, 'inXml' is created based on the 'RMD attribute value' passed on to it by 'redLedOnClicked' and 'redLedOffClicked' methods.


private String prepareInXml(int value) {
    inXml = "<inArgs><scannerID>" + scannerId + "<scannerID><cmdArgs><arg-int>" + value + "<arg-int><cmdArgs><inArgs>";
    return inXml;
}


private void performLedAction(String inXml) {
    if (scannerId != -1) {
        new ScannerLed(scannerId, DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_SET_ACTION).execute(new String[]{inXml});
    } 
    else {
        Toast.makeText(this, "Invalid scanner ID", Toast.LENGTH_SHORT).show();
    }
}

LED ON/OFF takes place when the 'Scanner ID' and the 'opcode' parameters are sent to the 'executeCommand' method.


private class ScannerLed extends AsyncTask<String,Integer,Boolean> {
    int scannerId;
    DCSSDKDefs.DCSSDK_COMMAND_OPCODE opcode;

    public ScannerLed(int scannerId,  DCSSDKDefs.DCSSDK_COMMAND_OPCODE opcode) {
        this.scannerId=scannerId;
        this.opcode=opcode;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected Boolean doInBackground(String... strings) {
        return  executeCommand(opcode,strings[0],null,scannerId);
    }

    @Override
    protected void onPostExecute(Boolean b) {
        super.onPostExecute(b);
    }
}


public boolean executeCommand(DCSSDKDefs.DCSSDK_COMMAND_OPCODE opCode, String inXml, StringBuilder outXml, int scannerId) {
    if (sdkHandler != null) {
        if(outXml == null) {
            outXml = new StringBuilder();
        }

        DCSSDKDefs.DCSSDK_RESULT result=sdkHandler.dcssdkExecuteCommandOpCodeInXMLForScanner(opCode,inXml,outXml,scannerId);

        if(result== DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_SUCCESS) {
            return true;
        }
        else if(result== DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_FAILURE) {
            return false;
        }
    }
    return false;
}

Firmware Update

Firmware can be updated using '.SCNPLG' files. These plugins can be obtained using the 123Scan Configuration Utility. Once 123Scan is installed, Plugin files for supported scanners can be found in 'C:\ProgramData\123Scan2\Plug-ins'.

First of all, permissions have to be included in the Manifest file for the device to access firmware files in the storage.


<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Secondly, the class 'IDcsSdkApiDelegate' must be implemented. Then the declaration of the String 'inXml' must take place.


public class MainActivity extends AppCompatActivity implements IDcsSdkApiDelegate {
    public static SDKHandler sdkHandler;
    private ArrayList<DCSScannerInfo> scannerInfoList = new ArrayList<DCSScannerInfo>();
    int scannerId;
    String inXml;
}

The object 'sdkHandler' should be notified of the implemented class. Then it is necessary to subscribe to events in order to be updated when the scanner gets reconnected, especially after a firmware update. The following code snippet takes care of that.


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    sdkHandler = new SDKHandler(this);
    DCSSDKDefs.DCSSDK_RESULT result = sdkHandler.dcssdkSetOperationalMode(DCSSDK_OPMODE_BT_NORMAL);

    sdkHandler.dcssdkSetDelegate(this);

    int notifications_mask = 0;

    // We would like to subscribe to all scanner available/not-available events
    notifications_mask |= DCSSDKDefs.DCSSDK_EVENT.DCSSDK_EVENT_SCANNER_APPEARANCE.value | DCSSDKDefs.DCSSDK_EVENT.DCSSDK_EVENT_SCANNER_DISAPPEARANCE.value;

    // We would like to subscribe to all scanner connection events
    notifications_mask |= DCSSDKDefs.DCSSDK_EVENT.DCSSDK_EVENT_SESSION_ESTABLISHMENT.value | DCSSDKDefs.DCSSDK_EVENT.DCSSDK_EVENT_SESSION_TERMINATION.value;

    // We would like to subscribe to all barcode events
    notifications_mask |= DCSSDKDefs.DCSSDK_EVENT.DCSSDK_EVENT_BARCODE.value;

    // subscribe to events set in notification mask
    sdkHandler.dcssdkSubsribeForEvents(notifications_mask);
}

With the implementation of 'IDcsSdkApiDelegate' interface the following methods will be implemented as well.


@Override
public void dcssdkEventScannerAppeared(DCSScannerInfo availableScanner) {
}

@Override
public void dcssdkEventScannerDisappeared(int scannerId) {
}

@Override
public void dcssdkEventCommunicationSessionEstablished(DCSScannerInfo activeScanner) {
}

@Override
public void dcssdkEventCommunicationSessionTerminated(int scannerId) {
}

@Override
public void dcssdkEventBarcode(byte[] barcodeData, int barcodeType, int fromScannerId) {
}

@Override
public void dcssdkEventFirmwareUpdate(FirmwareUpdateEvent firmwareUpdateEvent){
}

@Override
public void dcssdkEventAuxScannerAppeared(DCSScannerInfo newTopology, DCSScannerInfo auxScanner) {
}

@Override
public void dcssdkEventImage(byte[] imageData, int fromScannerId) {
}

@Override
public void dcssdkEventVideo(byte[] videoFrame, int fromScannerId) {
}

@Override
public void dcssdkEventBinaryData(byte[] binaryData, int fromScannerId) {
}

Once the 'Update Firmware' button of the given UI is clicked, the following 'updateFirmware' method gets called. Note that the location of the firmware file is given in the 'inXml' String. The 'UpdatingFirmware' 'AsyncTask' gets executed as the next statement fulfilling requested task.

Figure 6 Demo Application


public void updateFirmware(View view) {
    try {
        inXml = "<inArgs><scannerID>" + scannerId + "<scannerID><cmdArgs><arg-string>" + "/storage/emulated/0/Download/test.SCNPLG" + "<arg-string><cmdArgs><inArgs>";
        new UpdatingFirmware(scannerId, DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_UPDATE_FIRMWARE, null).execute(new String[]{inXml});
    }
    catch (Exception e) {
        Toast.makeText(getApplicationContext(),e.toString(),Toast.LENGTH_SHORT).show();
    }
}

Finally, 'executeCommand' is called when the 'scannerId','opcode' and 'outXml' are all set.


private class UpdatingFirmware extends AsyncTask {
    int scannerId;
    StringBuilder outXml;
    DCSSDKDefs.DCSSDK_COMMAND_OPCODE opcode;

    public UpdatingFirmware(int scannerId,  DCSSDKDefs.DCSSDK_COMMAND_OPCODE opcode,StringBuilder outXml) {
        this.scannerId = scannerId;
        this.opcode = opcode;
        this.outXml = outXml;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected Boolean doInBackground(String... strings) {
        return  executeCommand(opcode,strings[0],outXml,scannerId);
    }

    @Override
    protected void onPostExecute(Boolean b) {
        super.onPostExecute(b);
    }
}

public boolean executeCommand(DCSSDKDefs.DCSSDK_COMMAND_OPCODE opCode, String inXml, StringBuilder outXml, int scannerId) {
    if (sdkHandler != null) {
        if(outXml == null) {
            outXml = new StringBuilder();
        }
        
        DCSSDKDefs.DCSSDK_RESULT result=sdkHandler.dcssdkExecuteCommandOpCodeInXMLForScanner(opCode,inXml,outXml,scannerId);
        if(result== DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_SUCCESS) {
            return true;
        }
        else if(result== DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_FAILURE) {
            return false;
        }
    }
    return false;
}

Once a firmware update has started, user can be notified of the firmware update events. This is achieved by declaring a 'handler' inside 'dcssdkEventFirmwareUpdate(FirmwareUpdateEvent firmwareUpdateEvent)' method. Integer 'FW_UPDATE_EVENT' must be defined at the beginning of the class.


public class MainActivity extends AppCompatActivity implements IDcsSdkApiDelegate {
    public static SDKHandler sdkHandler;
    private ArrayList<DCSScannerInfo> scannerInfoList = new ArrayList<DCSScannerInfo>();

    int scannerId;
    String inXml;

    public static final int FW_UPDATE_EVENT = 35;
}

It is required to use handlers so as not to block the Event thread. With the use of a handler, the main thread will not be halted for the current process to execute. Multiple threads can run parallelly without interrupting each other.


@Override
public void dcssdkEventFirmwareUpdate(FirmwareUpdateEvent firmwareUpdateEvent) {
    dataHandler.obtainMessage(FW_UPDATE_EVENT,firmwareUpdateEvent).sendToTarget();
}

Inside the handler, 'firmwareUpdateEvent' parameter gets passed on to the method 'processFirmwareUpdateEvents'.


protected Handler dataHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        if(msg.what == FW_UPDATE_EVENT) {
            FirmwareUpdateEvent firmwareUpdateEvent=(FirmwareUpdateEvent)msg.obj;
            processFirmwareUpdateEvents (firmwareUpdateEvent);
        }
    }
}

Several types of Firmware Update events are available. Namely SCANNER_UF_SESS_START, SCANNER_UF_SESS_END, SCANNER_UF_DL_PROGRESS, SCANNER_UF_STATUS, SCANNER_UF_DL_START and SCANNER_UF_DL_END. SCANNER_UF_STATUS event will only take place in the event of a firmware update error.


private void processFirmwareUpdateEvents (FirmwareUpdateEvent firmwareUpdateEvent) {
    if(firmwareUpdateEvent.getEventType() == DCSSDKDefs.DCSSDK_FU_EVENT_TYPE.SCANNER_UF_SESS_START) {
        Log.i("ScannerControl","Update Firmware Session Started ! ");
    }

    if(firmwareUpdateEvent.getEventType() == DCSSDKDefs.DCSSDK_FU_EVENT_TYPE.SCANNER_UF_DL_PROGRESS) {
        Log.i("ScannerControl","Update Firmware DL Progress ! ");
    }

    if(firmwareUpdateEvent.getEventType() == DCSSDKDefs.DCSSDK_FU_EVENT_TYPE.SCANNER_UF_SESS_END) {
        try {
            Thread.sleep(1000);
        } 
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        startNewFirmware();
    }
}

Once the firmware update process is finished, it is required for the firmware to be launched on to the scanner and be rebooted. This is achieved by the code inside the event 'SCANNER_UF_SESS_END'. It is important for the working thread to sleep for a second before rebooting takes place.


private void startNewFirmware() {
    String inXml = "<inArgs><scannerID>" + scannerId + "</scannerID></inArgs>";
    StringBuilder outXml = new StringBuilder();

    executeCommand(DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_START_NEW_FIRMWARE, inXml, outXml, scannerId);
    Log.i("ScannerControl","Scanner Rebooted!");
}

As a final step, a call to 'executeCommand' method is made for the launching of the firmware on to the scanner to be completed. This can be verified with the rebooting beep of the scanner.


public boolean executeCommand(DCSSDKDefs.DCSSDK_COMMAND_OPCODE opCode, String inXml, StringBuilder outXml, int scannerId) {
    if (sdkHandler != null) {
        if(outXml == null) {
            outXml = new StringBuilder();
        }

        DCSSDKDefs.DCSSDK_RESULT result=sdkHandler.dcssdkExecuteCommandOpCodeInXMLForScanner(opCode,inXml,outXml,scannerId);

        if(result== DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_SUCCESS) {
            return true;
        }
        else if(result== DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_FAILURE) {
            return false;
        }
    }
    return false;
}

After rebooting of the scanner, it will be automatically reconnected to the app if Scan-To-Connect barcode had been used initially to connect the scanner to the app. If the connection had been made with the SSI BT Classic (Discoverable), scanner must be reconnected to the app manually. This can be achieved by pressing the 'connect' button in the UI. Once rebooting is completed and if the 'connect' button of the UI is pressed, the user can be notified of this reconnection by the 'dcssdkEventCommunicationSessionEstablished' event.


@Override
public void dcssdkEventCommunicationSessionEstablished(DCSScannerInfo activeScanner) {
    Log.i("ScannerControl","Scanner reconnected ! ");
}


Connect Scanner Using Scan-To-Connect (STC) Barcode

Preliminary environment setting has been described in previous sections. First it is ensured that the ACCESS_COARSE_LOCATION permission is given, if not, permission is set inside the 'onCreate' method along with the execution of the 'initializeDcsSdk' method where initializations take place.


public class MainActivity extends AppCompatActivity  {

    public static SDKHandler sdkHandler;
    public static ArrayList<DCSScannerInfo> scannerInfoList = new ArrayList<DCSScannerInfo>();

    int scannerId;
    static String bluetoothAddress;
    private FrameLayout barcodeDisplayArea;
    private EditText deviceBluetoothAddress;

    private static final int PERMISSIONS_ACCESS_COARSE_LOCATION = 10;
    private Dialog dialog ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        barcodeDisplayArea = (FrameLayout) findViewById(R.id.scan_to_connect_barcode);

        dialog = new Dialog(this);
        dialog.setContentView(R.layout.enter_bluetooth_address);
        deviceBluetoothAddress = (EditText) dialog.findViewById(R.id.device_bluetooth_address);
        sdkHandler = new SDKHandler(this);
        DCSSDKDefs.DCSSDK_RESULT result = sdkHandler.dcssdkSetOperationalMode(DCSSDK_OPMODE_BT_LE);

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // If permission has not been set previously , permission can be requested from the below code
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSIONS_ACCESS_COARSE_LOCATION);
        }
        else {
            initializeDcsSdk();
        }
    }
}


//The method that initializes the SDK
private void initializeDcsSdk() {
    sdkHandler.dcssdkEnableAvailableScannersDetection(true);
    sdkHandler.dcssdkSetOperationalMode(DCSSDKDefs.DCSSDK_MODE.DCSSDK_OPMODE_BT_LE);
    generatePairingBarcode();
}

Inside the 'initializeDcsSdk' method, Bluetooth operational mode is set and a call is made to 'generatePairingBarcode'.


@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
    switch (requestCode) {
        case PERMISSIONS_ACCESS_COARSE_LOCATION: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                initializeDcsSdk();
            } 
            else {
                finish();
            }
            return;
        }
    }
}


//Initial method that gets called to display the barcode based on the android version of the device
private void generatePairingBarcode() {
    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(-1, -1);

    BarCodeView barCodeView = sdkHandler.dcssdkGetPairingBarcode(DCSSDKDefs.DCSSDK_BT_PROTOCOL.SSI_BT_LE, DCSSDKDefs.DCSSDK_BT_SCANNER_CONFIG.SET_FACTORY_DEFAULTS);

    //For Android versions  7 and below  bluetooth address of the device will be taken by the SDK automatically. But for versions
    //above that barcodeView will be null , as bluetooth address cannot be retrieved via the SDK due to security measures.
    if(barCodeView!=null) {
        updateBarcodeView(layoutParams, barCodeView);
    }
    else {
        //when the barcodeView is null, a popup window will be visible where the bluetooth address of the device has to be entered
        // in order for the STC barcode to be visible.
        dialog.show();
    }
}

Inside the 'generatePairingBarcode', value of 'barCodeView' is checked. For devices with Android versions 6.xxx and below, the method 'updateBarcodeView' is called directly and the Bluetooth Address of the device is retrieved automatically rendering the STC barcode across the screen.

Figure 7 STC Pairing Barcode


// Once the correct bluetooth address is received this method proceed to display the barcode in the given frame layout
private void updateBarcodeView(LinearLayout.LayoutParams layoutParams, BarCodeView barcodeView) {
    Display display = getWindowManager().getDefaultDisplay();
    Point size = new Point();
    display.getSize(size);
    int width = size.x;
    int height = size.y;

    int orientation =this.getResources().getConfiguration().orientation;
    int x = width * 9 / 10;
    int y = x / 3;
    barcodeView.setSize(x, y);
    barcodeDisplayArea.addView(barcodeView, layoutParams);
}

The code snippet given below explains how the SDK manages to obtain the Bluetooth Address of Android 6.xxx and below versions. Please note that this snippet of code is already present in the SDK and doesn't need to be included in any custom application development.


private String btMacAddress;
private IDCConfig idcConfigObject;
private final BroadcastReceiver mPnPReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action != null) {
            if (action.equals("android.bluetooth.device.action.BOND_STATE_CHANGED")) {
                SDKHandler.this.handleBondStateChange(intent);
            } 
            else if (action.equals("android.bluetooth.device.action.FOUND")) {
                if (!SDKHandler.sessionPairedDevicesOnly) {
                    SDKHandler.this.handleScannerFound(intent);
                }
            } 
            else if (action.equals("android.bluetooth.device.action.ACL_DISCONNECTED")) {
                SDKHandler.this.handleScannerDisconnected(intent);
            } 
            else if (action.equals("android.hardware.usb.action.USB_DEVICE_ATTACHED")) {
                SDKHandler.this.handleUSBDeviceAttach(intent);
            } 
            else if (action.equals("android.hardware.usb.action.USB_DEVICE_DETACHED")) {
                SDKHandler.this.handleUSBDeviceDetach(intent);
            } 
            else if (action.equals("android.bluetooth.adapter.action.STATE_CHANGED")) {
                SDKHandler.this.handleBTAdapterStateChange(intent);
            }
        }

    }
};

public SDKHandler(Context context) {
    this.context = context;
    this.connMgrUSB = new USBManager(context);
    this.initializePnP();
    TAG = this.getClass().getSimpleName();
    this.bStopConnectionListner = true;
    this.bStartCOnnectionListner = true;
    DebugConfig.logAsMessage(DEBUG_TYPE.TYPE_DEBUG, TAG, "Initialized");
    scannerID = 1;
    Intent i = new Intent("symbol.intent.BTScannerService.stop");
    this.context.sendBroadcast(i);
    this.stopDiscoverySyncToken = new Object();
    this.pairWait = new Object();
    String macAddr = Secure.getString(this.context.getContentResolver(), "bluetooth_address");

    if (macAddr != null && !macAddr.endsWith("00:00:00:00:00")) {
        this.btMacAddress = macAddr;
    }

    this.idcConfigObject = new IDCConfig();
    this.pairedDevices = new HashSet();
}

For Android versions 7.0 and above value of 'barCodeView' will be null and displaying the STC barcode will require the user to manually enter the Bluetooth address of the android device. The following explanation of the UI screenshot is for the easy understanding of users with Android 7 or higher version devices. An EditText is provided in the dialog box for the user to enter the Bluetooth address. Once the Bluetooth Address is entered and the 'OK' button is pressed, the 'displayStc' method gets called after which the STC barcode gets displayed on the screen.

Figure 8 Device Bluetooth Address

Figure 9 Device Bluetooth Address

Figure 10 STC Pairing Barcode


//The method that is responsible for displaying the STC barcode according to the user provided bluetooth address
public void displayStc(View view) {
    if(dialog.isShowing()) {
        dialog.dismiss();
    }

    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(-1, -1);
    bluetoothAddress= deviceBluetoothAddress.getText().toString();
    Toast.makeText(getApplicationContext(),bluetoothAddress, Toast.LENGTH_SHORT).show();
    if(bluetoothAddress.equals("")) {
        barcodeDisplayArea.removeAllViews();
    }
    else {
        BarCodeView barcodeView;
        // User provided bluetooth address gets set
        sdkHandler.dcssdkSetBTAddress(bluetoothAddress);
        // Here a STC barcode corresponding to the bluetooth address provided will be generated
        barcodeView = sdkHandler.dcssdkGetPairingBarcode(DCSSDKDefs.DCSSDK_BT_PROTOCOL.SSI_BT_LE, DCSSDKDefs.DCSSDK_BT_SCANNER_CONFIG.SET_FACTORY_DEFAULTS, bluetoothAddress);
        if (barcodeView != null) {
            updateBarcodeView(layoutParams, barcodeView);
        }
    }
}

Here the barcode is generated based on the Bluetooth Address, Bluetooth Protocol and the Scanner Configuration provided to the method dcssdkGetPairingBarcode(DCSSDKDefs.DCSSDK_BT_PROTOCOL.SSI_BT_LE, DCSSDKDefs.DCSSDK_BT_SCANNER_CONFIG.SET_FACTORY_DEFAULTS, bluetoothAddress).

Given below is the set of available Bluetooth protocols in the Barcode Scanner SDK.


public static enum DCSSDK_BT_PROTOCOL {
    SSI_BT_CRADLE_HOST(22),
    SSI_BT_SSI_SLAVE(13),
    SSI_BT_LE(23),
    SSI_BT_MFI(19),
    CRD_BT(12),
    CRD_BT_LE(21),
    HID_BT(17),
    HID_BT_LE(20),
    SPP_BT_MASTER(14),
    SPP_BT_SLAVE(15),
    LEGACY_B(1);

    public int value;

    private DCSSDK_BT_PROTOCOL(int value) {
        this.value = value;
    }
}

Provided below is the set of supported Bluetooth scanner configurations in Barcode Scanner SDK.


public static enum DCSSDK_BT_SCANNER_CONFIG {
    KEEP_CURRENT(0),
    SET_FACTORY_DEFAULTS(1),
    RESTORE_FACTORY_DEFAULTS(2);

    public int value;

    private DCSSDK_BT_SCANNER_CONFIG(int value) {
        this.value = value;
    }
}

Code snippets extracted from Barcode Scanner SDK are for information purpose only and need not be included in the Java file.

Note that the operational mode set using the method, 'sdkHandler.dcssdkSetOperationalMode(DCSSDK_OPMODE_BT_LE)' should be compatible with the Bluetooth protocol used. For instance, if 'DCSSDK_OPMODE_BT_NORMAL' is used as the operational mode, then the Bluetooth protocol to be used is the 'SSI_BT_CRADLE_HOST'.

Scanner Connect Event

Once the STC barcode is available on the screen, the connection can be established by scanning the barcode with the Bluetooth scanner. The beep tone of the scanner confirms that the scanner has paired with the device as well as with the App.

Scanner Connect Event

If the user wishes to disconnect the scanner from the App as well as from the device, it can be achieved by pressing the 'Disconnect' button in the UI. Here the 'scannerId' is obtained from the 'scannerInfoList' and the method, 'dcssdkTerminateCommunicationSession(scannerId)' is provided with the obtained 'scannerId' to terminate the connection.


//The method that is responsible for disconnecting the scanner
public void disconnectScanner(View view) {
    try {
        //All connected scanners are in ActiveScannersList and the first scanner will be disconnected.
        sdkHandler.dcssdkGetActiveScannersList(scannerInfoList);
        scannerId = scannerInfoList.get(0).getScannerID();
        sdkHandler.dcssdkTerminateCommunicationSession(scannerId);
    }
    catch (Exception e){
        Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_SHORT).show();
    }
}

Scanner Enable/Disable

When the connection between the device and the scanner has been established, enabling and disabling the scanner can be achieved using the following two methods.

Figure 11 Scanner Enable/Disable

With the press of the 'ENABLE' button the following method gets called. When the OPCODE is given as 'DCSSDK_DEVICE_SCAN_ENABLE' along with the corresponding inXml, the scanner can be used for barcode scanning purpose.


//This method enables the barcode scanning functionality of the scanner
public void enableScanner(View view) {
    //first scanner in the scannerInfoList will get enabled
    sdkHandler.dcssdkGetActiveScannersList(scannerInfoList);
    scannerId = MainActivity.scannerInfoList.get(0).getScannerID();
    StringBuilder outXml = new StringBuilder();
    String inXml = "<inArgs><scannerID>" + scannerId + "</scannerID></inArgs>";
    // As it is not a production level application , an Async task has not been used here. But it is recommended to make use of an Async task
    sdkHandler.dcssdkExecuteCommandOpCodeInXMLForScanner(DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_DEVICE_SCAN_ENABLE, inXml, outXml);
}

With the press of the button 'DISABLE' barcode scanning functionality of the scanner will be disabled.


//This method disables the barcode scanning functionality of the scanner
public void disableScanner(View view) {
    //first scanner in the scannerInfoList will get disabled
    sdkHandler.dcssdkGetActiveScannersList(scannerInfoList);
    scannerId = MainActivity.scannerInfoList.get(0).getScannerID();
    StringBuilder outXml = new StringBuilder();
    String inXml = "<inArgs><scannerID>" + scannerId + "</scannerID></inArgs>";
    // As it is not a production level application , an Async task has not been used here. But it is recommended to make use of an Async task
    sdkHandler.dcssdkExecuteCommandOpCodeInXMLForScanner(DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_DEVICE_SCAN_DISABLE, inXml, outXml);
}


Get Currently Connected Scanner and Paired Devices

All the paired Bluetooth devices can be obtained using the API call 'dcssdkGetAvailableScannersList'.


public class MainActivity extends AppCompatActivity {
    public static SDKHandler sdkHandler;
    public static ArrayList scannerInfoList = new ArrayList();

    static String bluetoothAddress;
    private FrameLayout barcodeDisplayArea;
    private EditText deviceBluetoothAddress;

    private static final int PERMISSIONS_ACCESS_COARSE_LOCATION = 10;
    private Dialog dialog ;
}

First the array list must be initialized inside the class.


// The method that is responsible for giving the list of Available scanners
public void getAvailableScanners(View view) {
    sdkHandler.dcssdkGetAvailableScannersList(scannerInfoList);
    //Out of all the Available scanners, the name of the first scanner will be printed
    if(scannerInfoList.size()> 0) {
        Log.i("Available_scanner_01 :", scannerInfoList.get(0).getScannerName().toString());
    }
    else {
        Toast.makeText(getApplicationContext(),"No Available Scanners",Toast.LENGTH_SHORT).show();
    }
}

With the API call 'dcssdkGetActiveScannersList', information about the scanner that is paired with the device as well as connected to the app can be obtained. This is accomplished by the 'getActiveScanners' method.


// The method that gives the list of Active scanners
public void getActiveScanners(View view) {
    sdkHandler.dcssdkGetActiveScannersList(scannerInfoList);
    //Out of all the Active scanners, the name of the first scanner will be printed
    if(scannerInfoList.size()> 0) {
        Log.i("Active_scanner_01 :", scannerInfoList.get(0).getScannerName().toString());
    }
    else {
        Toast.makeText(getApplicationContext(),"No Active Scanners",Toast.LENGTH_SHORT).show();
    }
}

NOTE Due to a Google security patch, Google CVE-2020-12856, the Android device must notify users of pairing events with a popup message (prevents silent pairing).

Classic Mode Filtration

If the user wishes to filter only the Zebra devices while using 'DCSSDK_OPMODE_BT_NORMAL' as the operational mode, it can be obtained by enabling filtration. This is achieved by calling 'dcssdkEnableBluetoothClassicFiltration(Boolean)' method with Boolean 'true'.

When classic mode filtration is turned on, 'dcssdkGetAvailableScannersList' and 'dcssdkGetActiveScannersList' methods will only return Zebra devices.


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //initialize SDK
    sdkHandler = new SDKHandler(this);

    //Set operational mode
    sdkHandler.dcssdkSetOperationalMode(DCSSDK_OPMODE_BT_NORMAL);

    //Set boolean (true/false) for enabling and disabling filtration
    sdkHandler.dcssdkEnableBluetoothClassicFiltration(true);
}                                                            

If a Zebra scanner device's friendly name was changed it must be added in to the SDK by calling 'dcssdkAddCustomFriendlyName(String)' with the changed friendly name as the argument.


public void addNewFriendlyName(String friendlyName) {

    //Add friendly name
    sdkHandler.dcssdkAddCustomFriendlyName(friendlyName);
}

Scanner Auto Connection on App Relaunch in Bluetooth Low Energy mode

If user wishes to auto connect to the last connected scanner on app relaunch in BLE mode, IDcsScannerEventsOnReLaunch will be available to implement as an interface.


/**
* Should implement the interface to access event last connected scanner on app relaunch
* Activity implements IDcsScannerEventsOnReLaunch
* for activate/deactivate functionality and Ui notifications, messages and progress updates
*/
public interface IDcsScannerEventsOnReLaunch {
    /**
    * onLastConnectedScannerDetect method can be overridden when implementing.
    * @param device
    * @return (app setting has permission to connect last connected scanner on app relaunch)? true : false
    */
    boolean onLastConnectedScannerDetect(BluetoothDevice device);

    /**
    * onConnectingToLastConnectedScanner method can be overridden when implementing.
    * @param device
    */
    void onConnectingToLastConnectedScanner(BluetoothDevice device);

    /**
    * onScannerDisconnect method can be override on Activity.
    */
    void onScannerDisconnect();
}                                                           

App level UI implementation.


public class MainActivity implements IDcsScannerEventsOnReLaunch

Last connected scanner detection

Auto connection of last connected scanner on app relaunch can be activated by overriding 'boolean onLastConnectedScannerDetect (BluetoothDevice device)' and returning 'true'. By returning 'false' value, the option will be deactivated. Detected Bluetooth Device will be given as a parameter.


/**
* callback from BluetoothLEManager to show detected last connected device
* @param device - BluetoothDevice
* @return boolean - (application settings scanner connect to last scanner)?true:false
* if user wish to auto connect last connected device : return true
* default false
*/
@Override
public boolean onLastConnectedScannerDetect(BluetoothDevice device) {
    return true;
}

Connecting to the last connected scanner

By overriding 'void onConnectingToLastConnectedScanner (BluetoothDevice device)' user can perform UI updates. BlutoothDevice will be given as a parameter.


/**
* callback from BluetoothLeManager to show start connecting last connected device
* @param device
*/
@Override
public void onConnectingToLastConnectedScanner(BluetoothDevice device) {

}

Scanner disconnection

The callback will occur on scanner disconnection via BluetoothLEManager. By overriding 'void onScannerDisconnect()' user can perform UI updates.


/**
* callback from BluetoothLeManager to show disconnected device
*/
@Override
public void onScannerDisconnect() {

}