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"

Make Scanner Discoverable

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.


Implement Android Sample Project

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.

SDK Initialization

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 void initializeDcsSdk() {
    // Initializing sdk handler.
    if (sdkHandler == null) {
        sdkHandler = new SDKHandler(this, true);
    }

    sdkHandler.dcssdkSetDelegate(this);
    sdkHandler.dcssdkEnableAvailableScannersDetection(true);

    // Bluetooth low energy mode.
    sdkHandler.dcssdkSetOperationalMode(DCSSDKDefs.DCSSDK_MODE.DCSSDK_OPMODE_BT_LE);

    // Bluetooth classic mode.
    sdkHandler.dcssdkSetOperationalMode(DCSSDKDefs.DCSSDK_MODE.DCSSDK_OPMODE_BT_NORMAL);

    // SNAPI mode.
    sdkHandler.dcssdkSetOperationalMode(DCSSDKDefs.DCSSDK_MODE.DCSSDK_OPMODE_SNAPI);

    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);
}    

Request Runtime Permission

Before Initializing the sdk in onCreate() method, runtime permissions must be requested to connect scanner by Bluetooth.


private static final String[] BLE_PERMISSIONS = new String[]{
    android.Manifest.permission.ACCESS_COARSE_LOCATION,
    android.Manifest.permission.ACCESS_FINE_LOCATION,
};

private static final String[] ANDROID_13_BLE_PERMISSIONS = new String[]{
    android.Manifest.permission.BLUETOOTH_SCAN,
    android.Manifest.permission.BLUETOOTH_CONNECT,
    android.Manifest.permission.BLUETOOTH_ADVERTISE,
    android.Manifest.permission.ACCESS_FINE_LOCATION,
};

private static final String[] ANDROID_12_BLE_PERMISSIONS = new String[]{
    android.Manifest.permission.BLUETOOTH_SCAN,
    android.Manifest.permission.BLUETOOTH_CONNECT,
    android.Manifest.permission.BLUETOOTH_ADVERTISE,
    Manifest.permission.ACCESS_FINE_LOCATION,
};

/**
* Requesting runtime permission.
*/
private void requestPermission(){
    permissionsList = new ArrayList<>();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        permissionsList.addAll(Arrays.asList(ANDROID_13_BLE_PERMISSIONS));
    } 
    else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        permissionsList.addAll(Arrays.asList(ANDROID_12_BLE_PERMISSIONS));
    }
    else {
        permissionsList.addAll(Arrays.asList(BLE_PERMISSIONS));
    }
    askForPermissions(permissionsList);
}

/**
* Method to request required permissions with the permission launcher.
* @param permissionsList list of permissions.
*/
private void askForPermissions(ArrayList<String> permissionsList) {
    String[] newPermissionStr = new String[permissionsList.size()];
    for (int i = 0; i < newPermissionStr.length; i++) {
        newPermissionStr[i] = permissionsList.get(i);
    }

    if (newPermissionStr.length > 0) {
        permissionsLauncher.launch(newPermissionStr);
    } 
    else {
        showPermissionDialog();
    }
}


ActivityResultLauncher<String[]> permissionsLauncher =
    registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(),
        new ActivityResultCallback<Map<String, Boolean>>() {
            @RequiresApi(api = Build.VERSION_CODES.M)
            @Override
            public void onActivityResult(Map<String,Boolean> result) {
                ArrayList<Boolean> list = new ArrayList<>(result.values());
                permissionsList = new ArrayList<>();
                permissionsCount = 0;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                    for (int i = 0; i < list.size(); i++) {
                        if (shouldShowRequestPermissionRationale(ANDROID_13_BLE_PERMISSIONS[i])) {
                            permissionsList.add(ANDROID_13_BLE_PERMISSIONS[i]);
                        }
                        else if (!hasPermission(MainActivity.this, ANDROID_13_BLE_PERMISSIONS[i])) {
                            permissionsCount++;
                        }
                    }
                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
                    for (int i = 0; i < list.size(); i++) {
                        if (shouldShowRequestPermissionRationale(ANDROID_12_BLE_PERMISSIONS[i])) {
                            permissionsList.add(ANDROID_12_BLE_PERMISSIONS[i]);
                        }
                        else if (!hasPermission(MainActivity.this, ANDROID_12_BLE_PERMISSIONS[i])){
                            permissionsCount++;
                        }
                    }
                } else {
                    for (int i = 0; i < list.size(); i++) {
                        if (shouldShowRequestPermissionRationale(BLE_PERMISSIONS[i])) {
                            permissionsList.add(BLE_PERMISSIONS[i]);
                        }
                        else if (!hasPermission(MainActivity.this, BLE_PERMISSIONS[i])){
                            permissionsCount++;
                        }
                    }
                }
                if (permissionsList.size() > 0) {
                    //Some permissions are denied and can be asked again.
                    askForPermissions(permissionsList);
                } 
                else if (permissionsCount > 0) {
                    //Show alert dialog
                    showPermissionDialog();
                } 
                else {
                    //after all the required permissions are granted, the SDK should be initialized
                    initializeDcsSdk();
                }
            }
        });

/**
* Method to show dialog.
* To redirect to application settings if user denies the permissions.
*/
private void showPermissionDialog() {
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Permission required")
            .setCancelable(false)
            .setMessage("Some permissions need to be allowed for the seamless operation of the App.")
            .setPositiveButton("Settings", (dialog, which) -> {
                openAppSettings();
                dialog.dismiss();
        });
    if (alertDialog == null) {
        alertDialog = builder.create();
        if (!alertDialog.isShowing()) {
            alertDialog.show();
        }
    }
}

/**
* Method to check permissions granted status.
* @param context
* @param permissionStr
* @return true if permission granted otherwise false.
*/
private boolean hasPermission(Context context, String permissionStr) {
    return ContextCompat.checkSelfPermission(context, permissionStr) == PackageManager.PERMISSION_GRANTED;
}

/**
* Method to open application settings.
* User can grant the permission on the settings page as well.
*/
public void openAppSettings() {
    Intent intent = new Intent();
    intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    Uri uri = Uri.fromParts("package", MainActivity.this.getPackageName(), null);
    intent.setData(uri);
    MainActivity.this.startActivity(intent);
}                                                        

Enable Device Bluetooth

Once all permissions are granted, device Bluetooth enabling should be done prior to SDK initialization within the onCreate() method.


mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

// Device bluetooth enable if it is disabled.
if (!mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}                                                            

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();
    }
}


Connect Scanner

Scan-To-Connect (STC) Barcode

Within the onCreate() method, UI elements should be initialized. Then the user will be requested to grant the permissions that are required to use the application. Details on this can be found in Request runtime permission section. After runtime permissions are granted, SDK initialization should be done. More on this is described in SDK Initialization section.


public class MainActivity extends AppCompatActivity implements IDcsScannerEventsOnReLaunch,IDcsSdkApiDelegate
public static SDKHandler sdkHandler;
AlertDialog alertDialog;

ArrayList<String> permissionsList;
int permissionsCount = 0;
private FrameLayout barcodeDisplayArea;
private RelativeLayout stcLayout;
private Button btnDisconnect;
private TextView txtBluetoothMode;
int currentConnectedScannerID;
BluetoothAdapter mBluetoothAdapter;
private final static int REQUEST_ENABLE_BT = 1;

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

    // Initialization of UI components.
    barcodeDisplayArea = (FrameLayout) findViewById(R.id.scan_to_connect_barcode);
    btnDisconnect = (Button) findViewById(R.id.btn_disconnect);
    stcLayout = (RelativeLayout) findViewById(R.id.stc_layout);
    txtBluetoothMode = (TextView) findViewById(R.id.bluetooth_mode);

    // Change visibility on connection state change. Initially display the barcode.
    stcLayout.setVisibility(View.VISIBLE);
    btnDisconnect.setVisibility(View.GONE);

    btnDisconnect.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // Disconnect scanner on button click.
        disconnectScanner();
        }
    });

    requestPermission();
}    


/**
* 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;

    // Check the selected bluetooth protocol and generate barcode.
    if(btProtocol==DCSSDKDefs.DCSSDK_BT_PROTOCOL.SSI_BT_LE) {
    // Bluetooth Low Energy Mode.
        barCodeView = sdkHandler.dcssdkGetPairingBarcode(btProtocol, DCSSDKDefs.DCSSDK_BT_SCANNER_CONFIG.SET_FACTORY_DEFAULTS);
        if (barCodeView != null) {
            updateBarcodeView(layoutParams, barCodeView);
        }
    } 
    else {
        // SDK was not able to determine Bluetooth MAC. So call the dcssdkGetPairingBarcode with BT Address.
        // Bluetooth Classic Mode.
        String btAddress = getDeviceBTAddress(MainActivity.this);
        if (btAddress.equals("")) {
            barcodeDisplayArea.removeAllViews();
        } 
        else {
            sdkHandler.dcssdkSetBTAddress(btAddress);
            barCodeView = sdkHandler.dcssdkGetPairingBarcode(btProtocol, DCSSDKDefs.DCSSDK_BT_SCANNER_CONFIG.SET_FACTORY_DEFAULTS, btAddress);
            if (barCodeView != null) {
                updateBarcodeView(layoutParams, barCodeView);
            }    
        }
    }
}

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

    int orientation = this.getResources().getConfiguration().orientation;
    int x = width * 9 / 10;
    int y = x / 3;

    if (getDeviceScreenSize() > 6) { // TODO: Check 6 is ok or not
        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
            x = width / 2;
        } 
        else {
            x = width * 2 / 3;
        }
        y = x / 3;
    }
    barCodeView.setSize(x, y);
    barcodeDisplayArea.addView(barCodeView, layoutParams);
}

/**
* Get display size to utilize the UI.
* @return double
*/
private double getDeviceScreenSize() {
    double screenInches = 0;
    WindowManager windowManager = getWindowManager();
    Display display = windowManager.getDefaultDisplay();

    int mWidthPixels;
    int mHeightPixels;

    try {
        Point realSize = new Point();
        Display.class.getMethod("getRealSize", Point.class).invoke(display, realSize);
        mWidthPixels = realSize.x;
        mHeightPixels = realSize.y;
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        double x = Math.pow(mWidthPixels / dm.xdpi, 2);
        double y = Math.pow(mHeightPixels / dm.ydpi, 2);
        screenInches = Math.sqrt(x + y);
    } 
    catch (Exception e) {
        e.printStackTrace();
    }
    return screenInches;
}   

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 Disconnect 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 5: 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);
}

Wired Connection/SNAPI Mode


public class MainActivity extends AppCompatActivity implements IDcsSdkApiDelegate {
    public static SDKHandler sdkHandler;
    private FrameLayout barcodeDisplayArea;
    private RelativeLayout snapiLayout;
    private RelativeLayout brcodeLayout;
    RecyclerView recyclerView;

    int currentConnectedScannerID;
    static AvailableScanner curAvailableScanner = null;

    private static ArrayList<DCSScannerInfo> mSNAPIList = new ArrayList<DCSScannerInfo>();
    private static ArrayList<DCSScannerInfo> mScannerInfoList;

    ArrayList<Barcode> listScannedBarcodes;
    BarcodeListAdapter barcodeListAdapter;

    static ConnectAsyncTask cmdExecTask = null;

    public static CustomProgressDialog progressDialog;

    /**
    * The method that initializes the SDK.
    */
    @SuppressLint("MissingPermission")
    private void initializeDcsSdk() {
        // Initializing sdk handler.
        if (sdkHandler == null) {
            sdkHandler = new SDKHandler(this, true);
        }

        sdkHandler.dcssdkSetDelegate(this);
        sdkHandler.dcssdkEnableAvailableScannersDetection(true);
        sdkHandler.dcssdkSetOperationalMode(DCSSDKDefs.DCSSDK_MODE.DCSSDK_OPMODE_SNAPI);

        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);

        // Connect to scanner.
        connectToScanner();
    }

    /**
    * Get available scanner and connect.
    */
    private void connectToScanner(){
        mScannerInfoList = new ArrayList<>();
        mSNAPIList.clear();
        updateScannersList();
        for (DCSScannerInfo device : mScannerInfoList) {
            if (device.getConnectionType() == DCSSDKDefs.DCSSDK_CONN_TYPES.DCSSDK_CONNTYPE_USB_SNAPI) {
                mSNAPIList.add(device);
            }
        }

        if (mSNAPIList.isEmpty()) {
            // No SNAPI Scanners.
            getSnapiBarcode();
        } 
        else if (mSNAPIList.size() <= 1) {
            // Only one SNAPI scanner available.
            if (mSNAPIList.get(0).isActive()) {
                // Available scanner is active. Navigate to active scanner.
                currentConnectedScannerID = mSNAPIList.get(0).getScannerID();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // Change UI on connection.    
                    }
                });
                
            } 
            else {
                // Try to connect available scanner.
                cmdExecTask = new ConnectAsyncTask(mSNAPIList.get(0));
                cmdExecTask.execute();
            }
        }
    }

    @SuppressLint({"MissingInflatedId", "MissingPermission"})
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Initialization of UI components.
        barcodeDisplayArea = (FrameLayout) findViewById(R.id.scan_to_connect_barcode);
        snapiLayout = (RelativeLayout) findViewById(R.id.snapi_layout);

        // Change visibility on connection state change. Initially display the barcode.
        snapiLayout.setVisibility(View.VISIBLE);

        initializeDcsSdk();
    }

    /**
    * Initial method that gets called to display the barcode.
    * once scan the barcode Scanner change the protocol to SNAPI mode.
    */
    private void getSnapiBarcode() {
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(-1, -1);
        BarCodeView barCodeView = sdkHandler.dcssdkGetUSBSNAPIWithImagingBarcode();
        Display display = getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        int width = size.x;
        int orientation = this.getResources().getConfiguration().orientation;
        int x = width * 9 / 10;
        int y = x / 3;
        if (getDeviceScreenSize() > 6) { // TODO: Check 6 is ok or not
            if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                x = width / 2;
                y = x / 3;
            } 
            else {
                x = width * 2 / 3;
                y = x / 3;
            }
        }
        barCodeView.setSize(x, y);
        barcodeDisplayArea.addView(barCodeView, layoutParams);
    }

    /**
    * Get display size to utilize the UI.
    * @return double.
    */
    private double getDeviceScreenSize() {
        double screenInches = 0;
        WindowManager windowManager = getWindowManager();
        Display display = windowManager.getDefaultDisplay();

        int mWidthPixels;
        int mHeightPixels;

        try {
            Point realSize = new Point();
            Display.class.getMethod("getRealSize", Point.class).invoke(display, realSize);
            mWidthPixels = realSize.x;
            mHeightPixels = realSize.y;
            DisplayMetrics dm = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(dm);
            double x = Math.pow(mWidthPixels / dm.xdpi, 2);
            double y = Math.pow(mHeightPixels / dm.ydpi, 2);
            screenInches = Math.sqrt(x + y);
        } 
        catch (Exception e) {
            e.printStackTrace();
        }
        return screenInches;
    }
}

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 6: 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 7: Device Bluetooth Address

Figure 8: Device Bluetooth Address

Figure 9: 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'.


Implement SDK Features

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).

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 10: 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 11: 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 ! ");
}

Remote Scanner Management Solution (SMS)

SMS Packages are used for updating the firmware on the scanners and at the same time the configurations on the scanners could be modified with the configuration files which resides inside the SMS packages.

These files are created with the 123Scan configuration utility and the file extension is as .smspkg. Go through this doc on SMS package generation from 123Scan Configuration Utility.

Scanner SDK for Android provides an API for SMS execution using the above .smspkg files and following content will provide the necessary implementation guide on how to implement the basic SMS functionality.

First, permissions must be included in the Manifest file for the device to access SMS Package files in the storage.


<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" />
<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" />
<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.


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

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


@Override
public void dcssdkEventScannerAppeared(DCSScannerInfo dcsScannerInfo) {

}

@Override
public void dcssdkEventScannerDisappeared(int i) {

}

@Override
public void dcssdkEventCommunicationSessionEstablished(DCSScannerInfo dcsScannerInfo) {

}

@Override
public void dcssdkEventCommunicationSessionTerminated(int i) {

}

@Override
public void dcssdkEventBarcode(byte[] bytes, int i, int i1) {

}

@Override
public void dcssdkEventImage(byte[] bytes, int i) {

}

@Override
public void dcssdkEventVideo(byte[] bytes, int i) {

}

@Override
public void dcssdkEventBinaryData(byte[] bytes, int i) {

}

@Override
public void dcssdkEventFirmwareUpdate(FirmwareUpdateEvent firmwareUpdateEvent) {

}

@Override
public void dcssdkEventAuxScannerAppeared(DCSScannerInfo dcsScannerInfo, DCSScannerInfo dcsScannerInfo1) {

}

@Override
public void dcssdkEventConfigurationUpdate(ConfigurationUpdateEvent configurationUpdateEvent) {

}                                                            

After connecting to the scanner, user needs to provide the folder access permissions.


private static Uri persistedUri;
private static final int MY_PERMISSIONS_REQUEST_READ_WRITE_URI = 200;
private static final int MY_PERMISSIONS_REQUEST_WRITE_STORAGE = 100;

Call below method to request the necessary permissions to create the directories if not permission is not already provided.

If the permission is already provided next check whether the SMS package is there in the specified folder.


//Below method will verify that the .smspkg file is available or not.
private void displaySmsPackageName() {
    if (persistedUri != null) {
        Uri smsPackageUri = getSmsPackageUri(persistedUri);
        if (smsPackageUri != null && (smsPackageUri.getPath().endsWith(".smspkg") || smsPackageUri.getPath().endsWith(".SMSPKG"))) {
            String name = getSmsPackageName(persistedUri);
            tv_pkg.setText(name);
        } 
        else {
            Toast.makeText(MainActivity.this, "SMS Package file not found in the selected folder", Toast.LENGTH_SHORT).show();
        }
    } 
    else {
        requestRuntimePermissionToCreateDirs(MainActivity.this);
    }
}


//If .smspkg file is available then below method will return the path
public Uri getSmsPackageUri(Uri persistentUri) {
    Uri smsPackageUri = null;
    DocumentFile[] zebraSmsFiles = DocumentFile.fromTreeUri(this, persistentUri).listFiles();
    for (DocumentFile documentFile : zebraSmsFiles) {
        if (documentFile.getName().toLowerCase().endsWith(".smspkg")) {
            smsPackageUri = documentFile.getUri();
        }
    }

    return smsPackageUri;
}

//If .smspkg file is available then below method will return the file name
public String getSmsPackageName(Uri persistentUri) {
    String smsPackageName = null;
    DocumentFile[] zebraSmsFiles = DocumentFile.fromTreeUri(this, persistentUri).listFiles();
    for (DocumentFile documentFile : zebraSmsFiles) {
        if (documentFile.getName().toLowerCase().endsWith(".smspkg")) {
            smsPackageName = documentFile.getName();
        }
    }

    return smsPackageName;
}


//Read and Write permission is required to extract the SMS package file.
private void requestRuntimePermissionToCreateDirs(Context context) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        if (ActivityCompat.shouldShowRequestPermissionRationale((Activity) context, android.Manifest.permission.READ_EXTERNAL_STORAGE)) {
        } 
        else {
            ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_WRITE_STORAGE);
        }
    } 
    else {
        if (createDirectoryStructure(MainActivity.this)) {
            requestPersistentUriPermissions();
        }
    }
}

SMS Package file will be placed under Downloads/ZebraSMS folder and the folder can be created by using below code. Folder can be customized as per user needs.


//Passing the folder name to create a directory
private boolean createDirectoryStructure(Context context) {
    if (createDirectory("/ZebraSMS")) {
        return true;
    }
    Toast.makeText(context, "Error occurred during directory structure creation...", Toast.LENGTH_SHORT).show();

    return false;
}


//Creating a folder inside Downloads Public Directory
private boolean createDirectory(String filePath) {
    String toLocation = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath() + filePath;
    File directory = new File(toLocation);
    if (!directory.exists()) {
        return (directory.mkdir());
    } 
    else {
        return true;
    }
}

To access SMS Package file inside Download/ZebraSMS folder requires Run Time folder access permissions. User can customize the folder or give any public directory path for folder access permissions.


//Asking user to get the access of the specified folder
public void requestPersistentUriPermissions()  {
    if (!getContentResolver().getPersistedUriPermissions().isEmpty()) {
        List<UriPermission> permissions = getContentResolver().getPersistedUriPermissions();
        Collections.sort(permissions, new Comparator<UriPermission>() {
            public int compare(UriPermission o1, UriPermission o2) {
                return (String.valueOf(o2.getPersistedTime())).compareTo(String.valueOf(o1.getPersistedTime()));
            }
        });

        UriPermission p = permissions.get(0);

        if (p.getUri().toString().contains("ZebraSMS")) {
            this.persistedUri = p.getUri();
            displaySmsPackageName();
        } 
        else {
            startActivityForResult((new Intent("android.intent.action.OPEN_DOCUMENT_TREE")).putExtra("android.provider.extra.INITIAL_URI", DocumentsContract.buildDocumentUri("com.android.externalstorage.documents", "primary:Download/ZebraSMS")), MY_PERMISSIONS_REQUEST_READ_WRITE_URI);
        }
    } 
    else {
        startActivityForResult((new Intent("android.intent.action.OPEN_DOCUMENT_TREE")).putExtra("android.provider.extra.INITIAL_URI", DocumentsContract.buildDocumentUri("com.android.externalstorage.documents", "primary:Download/ZebraSMS")), MY_PERMISSIONS_REQUEST_READ_WRITE_URI);
    }
}

//Based on user response for Read/Write Permission, onRequestPermissionResult will be called
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_WRITE_STORAGE:
            if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
                Toast.makeText(MainActivity.this, "You denied storage permission which is needed to create directory structure..", Toast.LENGTH_SHORT).show();
            } 
            else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                if (createDirectoryStructure(MainActivity.this)) {
                    requestPersistentUriPermissions();
                }
            }
            break;
        default:
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

//Based on user response for folder access permission, onActivityResult will be called
@Override
public void onActivityResult(int paramInt1, int paramInt2, Intent paramIntent) {
    super.onActivityResult(paramInt1, paramInt2, paramIntent);
    if (paramInt1 == MY_PERMISSIONS_REQUEST_READ_WRITE_URI && paramInt2 == -1) {
        this.persistedUri = paramIntent.getData();
        this.getContentResolver().takePersistableUriPermission(paramIntent.getData(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        displaySmsPackageName();
    }
}

To start SMS execution you may invoke 'executeSMS' method below. Note that the location of the sms package file is given in the 'inXml' String. The 'SmsExecuteAsyncTask' gets executed as the next statement fulfilling requested task.


//Creating inXML for sms package execution
public void executeSms() {
    if(persistedUri != null){
        Uri smsPackageUri = getSmsPackageUri(persistedUri);
        if (smsPackageUri != null && (smsPackageUri.getPath().endsWith(".smspkg") || smsPackageUri.getPath().endsWith(".SMSPKG"))) {
            String in_xml = "<inArgs><cmdArgs><arg-string>" + smsPackageUri + "</arg-string></cmdArgs></inArgs>";
            SmsExecuteAsyncTask cmdExecTask = new SmsExecuteAsyncTask(scannerId,
                    DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_EXECUTE_SMS_PACKAGE, null);
            cmdExecTask.execute(new String[]{in_xml});
        } 
        else{
            Toast.makeText(MainActivity.this, "SMS Package Directory not selected", Toast.LENGTH_SHORT).show();
            requestPersistentUriPermissions();
        }
    }
}

Finally, 'dcssdkExecuteCommandOpCodeInXMLForScanner' is called when the 'scannerId','opcode', 'outXml' and the SMS package file path are all set.


//Asynctask to start the SMS Package Execution.
private class SmsExecuteAsyncTask extends AsyncTask<String, Integer, Boolean> {
    int scannerId;
    StringBuilder outXML;
    DCSSDKDefs.DCSSDK_COMMAND_OPCODE opcode;

    public SmsExecuteAsyncTask(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) {
        try{
            if (sdkHandler != null) {
                if (outXML == null) {
                    outXML = new StringBuilder();
                }
                DCSSDKDefs.DCSSDK_RESULT result = sdkHandler.dcssdkExecuteCommandOpCodeInXMLForScanner(opcode, strings[0], 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;
        }
        catch (Exception e) {
            Log.i(TAG,"ERRRRR: "+e.getLocalizedMessage());
            return false;
        }
    }
    
    @Override
    protected void onPostExecute(Boolean execCommandStatus) {
        super.onPostExecute(execCommandStatus);
        if (!execCommandStatus) {
            Toast.makeText(MainActivity.this, "SMS package execution failed..!", Toast.LENGTH_SHORT).show();
        } 
        else {
            Toast.makeText(MainActivity.this, "SMS package execution successful!", Toast.LENGTH_SHORT).show();
        }
    }
}

Once a firmware update has started, user can be notified of the firmware update events inside the 'dcssdkEventFirmwareUpdate(FirmwareUpdateEvent firmwareUpdateEvent)' method where 'firmwareUpdateEvent.getEventType()' provides the status of the firmware update.


//Status of Firmware Update Events
switch(firmwareUpdateEvent.getEventType()) {
    case SCANNER_UF_SESS_START:
        Log.i(TAG,"Firmware update started...");
        break;
    case SCANNER_UF_DL_START:
        Log.i(TAG,"Firmware downloading...");
        break;
    case SCANNER_UF_DL_PROGRESS:
        Log.i(TAG, "SCANNER_UF_DL_PROGRESS");
        break;
    case SCANNER_UF_DL_END:
        Log.i(TAG, "Firmware downloaded");
        break;
    case SCANNER_UF_SESS_END:
        Log.i(TAG, "Firmware updated successfully...");
        onRebootStarted();
        break;
    case SCANNER_UF_STATUS:
        Log.i(TAG, firmwareUpdateEvent.getStatus().toString());
        break;
}

If the scanner is not connected after reboot with the Firmware Update, try to do it using pairing barcode and call the below method to trigger the configuration update process.


SmsPackageUpdateManager.getInstance().onScannerConnected();

Once a Configuration update has started, user can be notified of the configuration update events inside the 'dcssdkEventConfigurationUpdate(ConfigurationUpdateEvent configurationUpdateEvent)' method where 'configurationUpdateEvent.getEventType()' provides the status of the configuration update.


//Status of Configuration Update Events
switch (configurationUpdateEvent.getEventType()) {
    case SCANNER_UC_SESS_START:
        Log.i(TAG, "Configuration updated started...");
        break;
    case SCANNER_UC_PROGRESS:
        Log.i(TAG, "Configuration update in progress");
        break;
    case SCANNER_UC_SESS_END:
        Log.i(TAG, "Configuration update completed...");
        isRebootSuccess = false;
        break;
    case SCANNER_UC_STATUS:
        Log.i(TAG,"Configuration update failed...");
        persistedUri = null;
        isRebootSuccess = false;
        break;
}                                                            

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() {

}


Virtual Tether

Virtual Tether is used to alert the user when the scanner is interrupted or taken out of the range of the host device. The attribute command DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_RSM_ATTR_STORE is used to store the value for enabling or disabling the virtual tether alarm.

The below xml string attribute is used for Enabling the Virtual Tether feature.


// Example: Scanner ID is 1 and RMD_ATTR_VIRTUAL_TETHER_ALARM_STATUS is 2053, and RMD_ATTR_VALUE_VIRTUAL_TETHER_ALARM_ENABLE is 1 to enable the virtual tether or RMD_ATTR_VALUE_VIRTUAL_TETHER_ALARM_ENABLE is 0 to disable the virtual tether.

String inXML = 
    "<inArgs>
        <scannerID>" + scannerID + "</scannerID>
        <cmdArgs>
            <arg-xml>
                <attrib_list>
                    <attribute>
                        <id>" + RMD_ATTR_VIRTUAL_TETHER_ALARM_STATUS + "</id>
                        <datatype>B</datatype>
                        <value>" + RMD_ATTR_VALUE_VIRTUAL_TETHER_ALARM_ENABLE + "</value>
                    </attribute>
                </attrib_list>
            </arg-xml>
        </cmdArgs>
    </inArgs>";

Pass the above string to async task to execute the command and get the results.


VirtualTetherAsyncTask vtAsyncTask = new VirtualTetherAsyncTask(scannerID, DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_RSM_ATTR_STORE);
vtAsyncTask.execute(in_xml);

Below is an AsyncTask code to enable or disable the virtual tethering.


private class VirtualTetherAsyncTask extends AsyncTask {
    int scannerId;
    DCSSDKDefs.DCSSDK_COMMAND_OPCODE opcode;

    public VirtualTetherAsyncTask(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) {
        StringBuilder outXML = new StringBuilder();
        DCSSDKDefs.DCSSDK_RESULT result = Application.sdkHandler.dcssdkExecuteCommandOpCodeInXMLForScanner(opCode, strings[0], outXML, scannerID);
        if (result == DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_SUCCESS) {
            return true;
        }
        else {
            return false;
        }
    }

    @Override
    protected void onPostExecute(Boolean b) {
        super.onPostExecute(b);
        if(b) {
            // Virtual Tether Update Success.
        }
        else {
            // Virtual Tether Update Failed.
        }
    }
}                                                            

Below given code can be used to simulate virtual tether audio, led, haptics and illumination alarms. In here, the command attribute DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_SET_ACTION is used.


// Example: ScannerID is 1 and ACTION_COMMAND_VIRTUAL_TETHER_START_SIMULATION is 232

String in_XML_Simulation = 
    "<inArgs>
        <scannerID>" + scannerID + "</scannerID>
        <cmdArgs>
            <arg-int>" + ACTION_COMMAND_VIRTUAL_TETHER_START_SIMULATION + "</arg-int>
        </cmdArgs>
    </inArgs>";

VirtualTetherAsyncTask vtAsyncTask = new VirtualTetherAsyncTask(scannerID, DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_SET_ACTION);
vtAsyncTask.execute(in_xml_for_simulation);