Android Question Intercepting hardware buttons?

rlocke

Member
Hello,

We are using this Android device that has a built-in 2D scanner (Munbyn Android 9 barcode scanner):

https://amzn.to/3FRXEwv

There’s a dedicated button (on the left side) that scans the barcode - if the cursor is in a text box, the barcode is outputted to it.

My question: is it possible to intercept the button being pressed, and instead of having the barcode outputted to a text field, save it to a variable? My usage scenario:
  1. User launches my B4A app
  2. They scan a barcode, but instead of outputting the barcode in a text field, it’s saved to a SQLite database
Basically, I would like to program some action to happen when I press the scan button, and then capture the barcode data for later processing/storage.

Do I need to intercept the hardware button? Or do I need to be looking into intents (there’s a SDK for the device).

Thank you.
 

udg

Expert
Licensed User
A simple way could be to have an "hidden" text field. The barcode reading still goes there, you manage it as you like and don't need to directly interact with the hardware of the reader (i.e. its dedicated button) or an eventual SDK to intercept events.
 
Upvote 0

agraham

Expert
Licensed User
It depends on how the button is handled by Android on the device. It might well come through the Activity_KeyPress event and you can monitor it there. However there is probably not much you can do other than wait for a time then see if a bar-code popped up somewhere.

I'd look at the SDK to see what you can do there.
 
Upvote 0

Gandalf

Member
Licensed User
Take a look at device's SDK documentation. There can be an intent action name for scan event. From this intent you can try to get the scanned code. What regards to intercepting hardware buttons - first you need to stop or disable the vendor's scan app which reacts to the button, otherwise it will intercept it first. Having intent action name and nothing intercepting button presses before your app does, it becomes quite easy. For example, service module named HWcontrols intercepts hardware Push-To-Talk button:
B4X:
Sub Service_Start(StartingIntent As Intent)
    If StartingIntent.Action = "android.intent.action.PTT.down" Then
        Log("PTT down")
    else if StartingIntent.Action = "android.intent.action.PTT.up" Then
        Log("PTT up")
    End If
End Sub

And this must be added in Manifest Editor:
B4X:
AddReceiverText(HWcontrols,
<intent-filter>
    <action android:name="android.intent.action.PTT.down"/>
</intent-filter>)
AddReceiverText(HWcontrols,
<intent-filter>
    <action android:name="android.intent.action.PTT.up"/>
</intent-filter>)

Please note that your service module name should be properly indicated in Manifest Editor text.
 
Upvote 0

rlocke

Member
Hi @Gandalf - thank you for the info. I looked through their SDK, it's not very well documented (it's mostly code samples) and couldn't find any examples of their scanner's intents.

When I press the scanner button, it doesn't launch their scanner app, so I dropped this code into my Main module to sniff out the button's key code:

B4X:
Sub Activity_KeyPress (KeyCode As Int) As Boolean
    MsgboxAsync(KeyCode, "Keycode")
    Return B4XPages.Delegate.Activity_KeyPress(KeyCode)
End Sub

And discovered the following key codes:
  • Left scanner button = 308
  • Middle scanner button = 307
  • Right scanner button = 309
So now I know when one of the scanner button's is pressed and take some action, but I guess I still need to intercept the scanner's intent so I can capture the actual barcode?

Looking into the source code that was included in their SDK, these are the only intents I saw in the manifest file:
Manifest:
<application
        android:icon="@mipmap/scan"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity android:name=".TestScanActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

FWIW, here's their SDK:

https://www.transfernow.net/en/dltransfer?utm_source=202110131mZtqMXV
 
Last edited:
Upvote 0

rlocke

Member
And the sample app's java source code (maybe someone sees something I'm not):

Code.java:
package com.example.testscan;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.device.ScanDevice;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;

public class TestScanActivity extends Activity {
  ScanDevice sm;
  private final static String SCAN_ACTION = "scan.rcv.message";
  private String barcodeStr;
  private EditText showScanResult;

  private BroadcastReceiver mScanReceiver = new BroadcastReceiver() {

    @Override
    public void onReceive(Context context, Intent intent) {

      byte[] barocode = intent.getByteArrayExtra("barocode");
      int barocodelen = intent.getIntExtra("length", 0);
      byte temp = intent.getByteExtra("barcodeType", (byte) 0);
      byte[] aimid = intent.getByteArrayExtra("aimid");
      barcodeStr = new String(barocode, 0, barocodelen);
      showScanResult.append(barcodeStr);
      showScanResult.append("\n");
      sm.stopScan();
    }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.testcan_main_activity);
    sm = new ScanDevice();
    sm.setOutScanMode(0);//启动就是广播模式
    Spinner spinner = (Spinner) findViewById(R.id.spinner);
    String[] arr = { getString(R.string.brodcast_mode), getString(R.string.input_mode) };
    //创建ArrayAdapter对象
    ArrayAdapter<String> adapter =
        new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, arr);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);
    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
      @Override
      public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if (position == 0) {
          sm.setOutScanMode(0);
        } else if (position == 1) {
          sm.setOutScanMode(1);
        }
      }
      @Override public void onNothingSelected(AdapterView<?> parent) {

      }
    });

    showScanResult = (EditText) findViewById(R.id.editText1);
  }

  public void onClick(View v) {
    switch (v.getId()) {
      case R.id.openScanner:
        System.out.println("openScanner = " + sm.getOutScanMode());
        sm.openScan();
        break;
      case R.id.closeScanner:
        sm.closeScan();
        break;
      case R.id.startDecode:
        sm.startScan();
        break;
      case R.id.stopDecode:
        sm.stopScan();
        break;
      case R.id.start_continue:
        sm.setScanLaserMode(4);
        break;
      case R.id.stop_continue:
        sm.setScanLaserMode(8);
        break;
      case R.id.close:
        finish();
        break;
      case R.id.clear:
        showScanResult.setText("");
        break;
      default:
        break;
    }
  }

  @Override
  protected void onPause() {
    super.onPause();
    unregisterReceiver(mScanReceiver);
  }

  @Override
  protected void onResume() {
    super.onResume();
    IntentFilter filter = new IntentFilter();
    filter.addAction(SCAN_ACTION);
    registerReceiver(mScanReceiver, filter);
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    if (sm != null) {
      sm.stopScan();
      sm.setScanLaserMode(8);
      sm.closeScan();
    }
  }
}
 
Upvote 0

rlocke

Member
So I tried to sniff out the scanner intents - in the Scanner setting app, I changed the mode to "Broadcast":

settings.jpg


And then I ran this app: https://play.google.com/store/apps/details?id=lt.andro.broadcastlogger&hl=de

However, when I press the scanner button, no barcode intents show up.
 
Upvote 0

Gandalf

Member
Licensed User
Unfortunately my Java knowledge is quite poor. What I see from their code:
Action name is "scan.rcv.message" and this is the one you should intercept.
The barcode data comes out as byte array in extra data of this intent:
Java:
byte[] barocode = intent.getByteArrayExtra("barocode");
int barocodelen = intent.getIntExtra("length", 0);
Please note the "barocode" instead of "barcode" - I don't know if it is just typo or these data fields are really named like that.
Take a look here for intent extra data.
Then you need to convert byte array to String.
 
Upvote 0

Nataly

Member
Hi @rlocke , So according to the code the action/intent name is "scan.rcv.message", you can intercept the intent in two ways if the first didn't work the second should (in the code you provided above they used the second method),

First you need to add the intent in the manifest file from B4A, So:
In B4A Go to Project ->Manifest editor -> add the following line:

XML:
AddReceiverText(ServiceModuleName,

<intent-filter android:priority="1000" >
<action android:name="scan.rcv.message"/>
    
</intent-filter>)

Method 1:

B4X:
Sub Service_Start(StartingIntent As Intent)
    If StartingIntent.Action = "scan.rcv.message" Then
        '' You can check/get extras using the following methods
        Log("Extras: "&StartingIntent.ExtrasToString)
        Dim Barcode() As Byte = StartingIntent.GetExtra('barcode')
      
        '' ...etc
    End If
End Sub

Method 2 - Broadcast Receiver:

1- Make sure you installed the BroadcastReceiver library (seach the forum) and add it from library manager in B4A.
2- Create a new class module (standard class): Project ->Add new module.
3-
B4X:
Sub Class_Globals
    Dim Broadcast As BroadCastReceiver
End Sub

Public Sub Initialize
    Broadcast.Initialize("BroadcastReceiver")
    Broadcast.addAction("scan.rcv.message")
    Broadcast.SetPriority(2147483647)
    Broadcast.registerReceiver("") 'here you can add the main action (intent)
End Sub


Sub BroadcastReceiver_OnReceive (Action As String, Extras As Object)
    If Action = "scan.rcv.message" Then
        ''...etc
    End If

4- In starter Service (service), you must initialize the class that has the broadcast receiver:
B4X:
Sub Service_Create
    Dim var As NEW_CLASS_NAME
    var.Initialize
End Sub
 
Upvote 0

rlocke

Member
Hi @Gandalf,

Yes, you were right - I started going down the path of intercepting "scan.rcv.message", and I finally got everything working (with some detective work on the hardware side).

Just in case anyone stumbles across this thread, here's how I got it working:
  1. Looking through the scanner's spec sheet, I found out that the scanner is a Honeywell 6603 model (part of the 660X series)
  2. Found this Honeywell article: https://support.honeywellaidc.com/s/article/How-to-use-the-Barcode-Data-Intent (the important part was finding: com.honeywell.decode.permission.DECODE)
  3. This post also helped me figure out how to get everything working: https://www.b4x.com/android/forum/threads/zebra-tc20-with-intent.120024/post-751230
I ended up not using any Android intents in the manifest file, and instead used the Broadcast Receiver library, based on what their tech support team said:

you need to register the Broadcast receiver, and then get the barcode information after receiving the broadcast.

The service module's code:

ServiceModule:
#Region  Service Attributes
    #StartAtBoot: true
   
#End Region

Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    'https://www.b4x.com/android/forum/threads/zebra-tc20-with-intent.120024/post-751230
    Dim BC As BroadCastReceiver
    Dim structure As List
End Sub


Sub Service_Create
    Log("Service_Create")
   
    BC.Initialize("BC")
    structure.Initialize()
    structure.Add("com.honeywell.decode.permission.DECODE")
End Sub


Sub Service_Start (StartingIntent As Intent)
   
    'Log("Service_Start")
   
    'BC.addCategory("intent_kategorie")
    BC.addAction("scan.rcv.message")
    BC.SetPriority(20000000)
    BC.registerReceiver("scan.rcv.message")
    Log("Scanner Service extras: " & StartingIntent.ExtrasToString)

    Service.StopAutomaticForeground 'Call this when the background task completes (if there is one)
   
End Sub


Sub BC_OnReceive (action As String, o As Object)

    Dim intent As Intent = o
    Log(intent.ExtrasToString)
   
    'Log("barocode = " & intent.GetExtra("barocode"))
    'Log("length = " & intent.GetExtra("length"))
   
   
    'extract and decode the barcode
    Dim jo As JavaObject = o
    Dim content() As Byte = jo.RunMethod("getByteArrayExtra", Array("barocode"))
    Dim barcode As String
    barcode = BytesToString(content, 0, content.Length, "UTF8")
    Log("Barcode = " & barcode & " (BC_OnReceive)")
   
    CallSubDelayed2(B4XPages.MainPage, "DisplayBarcode", barcode)

End Sub


Sub Service_Destroy

End Sub

In the BC_OnReceive function, I extract and parse the barcode info (FYI, it is actually called "barocode").

And the Main Page:

B4XMainPage:
Sub Class_Globals
    Private Root As B4XView 'ignore
    Private xui As XUI 'ignore
    Public PageTest As B4XPageTest
    Private lblBarcode As Label
End Sub

'You can add more parameters here.
Public Sub Initialize

End Sub


'This event will be called once, before the page becomes visible.
Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    Root.LoadLayout("Login")
   
    B4XPages.SetTitle(B4XPages.MainPage, "Barcode Reader")
           
    'Test page
    PageTest.Initialize
    B4XPages.AddPage("Page Test", PageTest)
End Sub


Sub B4XPage_Appear

End Sub

Sub DisplayBarcode(Barcode As String)
    Log(Barcode)
    lblBarcode.Text = "Barcode = " & Barcode
End Sub

I've attached the project, and their developer docs for reference.

Looking back, one of the hurdles was their poor SDK documentation and examples, and the fact that the Honeywell didn't broadcast their intents (like with a Zebra device and its Data Wedge application). I think it might have been a little quicker had we used this device (which uses Zebra):

https://amzn.to/3BOTvac

Thanks everyone!
 

Attachments

  • B4AProject.zip
    245.5 KB · Views: 5
  • IPDA030 Scan Development-Instructions.zip
    226.5 KB · Views: 3
Upvote 0

rlocke

Member
So a quick followup question: if I leave my app open and let the phone go to sleep (i.e.: the screen turns off and I have to unlock it), when I go back to the app and press the scan button, the barcode isn't read. I have to force quit the app and restart it, and then it reads the barcode again. Any thoughts on how to prevent this?

My project uses B4X Pages/Modules.
 
Upvote 0
Top