Skip to content

Commit

Permalink
Add Zebra DataWedge provider (#126)
Browse files Browse the repository at this point in the history
* Added Zebra DW provider
* Fixed #112 : worked around camera lifecycle.
* Fixed DW provider start/stop API
* Fixed incomplete CameraV2 image
* Activity: Fixed LED button always present in default layout
* Closes #113 - Zebra DW provider now creates its own profile.
* Fixed API version
* Fixed API 24 call
* Fixed #124: IntentProvider service test
* Fixed #124: ZebraDwProvider compatibility test
* Add <queries> tag to manifest for DW package
* Tentative fix for #111 - camera init/close is now locked globally
* Fixed #113 remark - DW profile switching.

---------

Co-authored-by: Marc-Antoine Gouillart <[email protected]>
  • Loading branch information
DaSpood and marcanpilami authored Oct 6, 2023
1 parent e415d74 commit 7126f92
Show file tree
Hide file tree
Showing 18 changed files with 1,136 additions and 25 deletions.
8 changes: 6 additions & 2 deletions demoscannerapp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,20 @@ dependencies {
implementation fileTree(dir: '../enioka_scan_honeywell/libs', include: ['*.aar']);
}

if (gradle.ext.withKoamtac)
if (gradle.ext.withKoamtac) {
implementation project(':enioka_scan_koamtac');
}

if (gradle.ext.withZebra) {
implementation project(':enioka_scan_zebra');
implementation fileTree(dir: '../enioka_scan_zebra/libs', include: ['*.aar'])
}

if (gradle.ext.withM3)
if (gradle.ext.withM3) {
implementation project(':enioka_scan_m3');
}

implementation project(':enioka_scan_zebra_dw');

// Android stuff

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -616,22 +616,41 @@ protected void displayCameraPauseToggle() {
private boolean ledToggle = false;

private void displayToggleLedButton() {
if (findViewById(R.id.scanner_red_led) != null) {
findViewById(R.id.scanner_red_led).setOnClickListener(view -> {
if (!ledToggle) {
for (final Scanner s : ScannerCompatActivity.this.scannerService.getConnectedScanners()) {
if (s.getLedSupport() != null)
s.getLedSupport().ledColorOn(ScannerLedColor.RED);
}
} else {
for (final Scanner s : ScannerCompatActivity.this.scannerService.getConnectedScanners()) {
if (s.getLedSupport() != null)
s.getLedSupport().ledColorOff(ScannerLedColor.RED);
}
}
ledToggle = !ledToggle;
});
// Button present in layout?
if (findViewById(R.id.scanner_red_led) == null) {
return;
}
View v = findViewById(R.id.scanner_red_led);

// Check if we should display the button
boolean anySupport = false;
for (final Scanner s : ScannerCompatActivity.this.scannerService.getConnectedScanners()) {
if (s.getLedSupport() != null) {
anySupport = true;
break;
}
}
if (!anySupport) {
v.setVisibility(View.GONE);
return;
}

// Set the event handler
v.setOnClickListener(view -> {
if (!ledToggle) {
for (final Scanner s : ScannerCompatActivity.this.scannerService.getConnectedScanners()) {
if (s.getLedSupport() != null)
s.getLedSupport().ledColorOn(ScannerLedColor.RED);
}
} else {
for (final Scanner s : ScannerCompatActivity.this.scannerService.getConnectedScanners()) {
if (s.getLedSupport() != null)
s.getLedSupport().ledColorOff(ScannerLedColor.RED);
}
}
ledToggle = !ledToggle;
});

}

private void displayDisableScanButton() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,10 +368,11 @@ private int handleInputBufferLoop(byte[] buffer, int offset, int length) {
}

if (!res.expectingMoreData && res.data != null) {
Log.d(LOG_TAG, "Data was interpreted as: " + res.data.toString());
Log.d(LOG_TAG, "Data was interpreted as: " + res.data.toString() + " (" + res.getClass().getSimpleName() + ")");

// ACK first - the event handlers may write to stream and create out of order ACKs.
if (res.acknowledger != null) {
Log.d(LOG_TAG, "This command requires an ACK from this device");
this.outputStreamWriter.endOfCommand();
this.outputStreamWriter.write(res.acknowledger.getCommand(this), true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
* V2 implementation of the camera view. Default implementation.
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
class CameraBarcodeScanViewV2 extends CameraBarcodeScanViewBase<Image> {
private static final Semaphore criticalInitBlock = new Semaphore(1);
private String cameraId;
private CameraManager cameraManager;

Expand Down Expand Up @@ -90,6 +93,22 @@ private void init() {
startBackgroundThread();
}

private void acquire() {
try {
if (!criticalInitBlock.tryAcquire(10, TimeUnit.SECONDS)) {
throw new IllegalStateException("cannot acquire camera semaphore - it may be locked by another activity/fragment");
}
} catch (InterruptedException e) {
if (Thread.currentThread().isInterrupted()) {
throw new RuntimeException(e);
}
}
}

private void release() {
criticalInitBlock.release();
}

private void selectCameraParameters() {
// Should be tested before but let's check for sanity's sake anyway.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
Expand Down Expand Up @@ -326,6 +345,7 @@ private void openCamera() {
if (!hasPermissionSet(getContext(), PERMISSIONS_CAMERA)) {
throw new RuntimeException("missing use camera permission");
}
acquire();

CameraManager manager = (CameraManager) getContext().getSystemService(android.content.Context.CAMERA_SERVICE);
if (manager == null) {
Expand Down Expand Up @@ -400,9 +420,17 @@ public void pauseCamera() {
// TODO: add an actual pause, by stopping the repeating loop of the session instead of destroying it.
if (this.captureSession != null) {
Log.i(TAG, "Stopping capture session");
this.imageReader.close();
try {
this.imageReader.close();
} catch (IllegalArgumentException e) {
Log.i(TAG, "image reader would not close");
}
this.imageReader = null;
this.captureSession.close();
try {
this.captureSession.close();
} catch (IllegalArgumentException e) {
Log.i(TAG, "captureSession would not close");
}
this.captureSession = null;
}
post(() -> {
Expand Down Expand Up @@ -555,6 +583,7 @@ public void onClosed(@NonNull CameraDevice camera) {
croppedImageBufferQueue.clear();

stopping = false;
release();
Log.i(TAG, "Camera scanner view has finished releasing all camera resources " + CameraBarcodeScanViewV2.this.hashCode());
}

Expand Down Expand Up @@ -642,6 +671,10 @@ public void onConfigured(@NonNull final CameraCaptureSession cameraCaptureSessio

// Capture targets
captureRequestBuilder.addTarget(CameraBarcodeScanViewV2.this.camPreviewSurfaceView.getHolder().getSurface());
if (imageReader == null) {
Log.d(TAG, "stopping init - view is being stopped");
return;
}
captureRequestBuilder.addTarget(imageReader.getSurface());

// Full auto (without this AF & AE are mostly disabled)
Expand Down Expand Up @@ -729,7 +762,10 @@ public void onClosed(@NonNull CameraCaptureSession session) {
FrameAnalysisContext<Image> ctx = new FrameAnalysisContext<>();
try {
// Sanity check
assert (image.getPlanes().length == 3);
if (image.getPlanes().length != 3 || image.getPlanes()[0] == null) {
image.close();
return;
}

// Sanity check
if (image.getPlanes()[0].getPixelStride() != 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public abstract class IntentScanner<BarcodeTypeClass> extends BroadcastReceiver
protected ScannerStatusCallbackProxy statusCb = null;
protected Scanner.Mode mode;
protected Set<BarcodeType> symbologies;
protected boolean paused = true;


@Override
Expand All @@ -70,6 +71,7 @@ public void initialize(final Context applicationContext, final ScannerInitCallba

// Let the child provider set all the configuration values if needed.
configureProvider();
configureProvider(ctx);

// Register the broadcast receiver.
registerReceivers(ctx, initCallback, statusCallback);
Expand All @@ -85,6 +87,7 @@ public void initialize(final Context applicationContext, final ScannerInitCallba
if (this.statusCb != null) {
this.statusCb.onStatusChanged(this, ScannerStatusCallback.Status.READY);
}
paused = false;
}

protected void registerReceivers(Context ctx, ScannerInitCallbackProxy initCallback, ScannerStatusCallbackProxy statusCallback) {
Expand All @@ -103,6 +106,13 @@ protected void registerReceivers(Context ctx, ScannerInitCallbackProxy initCallb
protected void configureProvider() {
}

/**
* Called just before initial scanner initialization. Empty by default. An occasion to set the configuration fields.
*/
protected void configureProvider(final Context applicationContext) {

}

/**
* Called just after initial scanner initialization. Empty by default. An occasion to set different parameters on the scanner, like enabled symbologies.
*/
Expand Down Expand Up @@ -134,6 +144,7 @@ public void pause(@Nullable ScannerCommandCallbackProxy cb) {
} else if (cb != null) {
cb.onFailure();
}
paused = true;
}

@Override
Expand All @@ -147,6 +158,7 @@ public void resume(@Nullable ScannerCommandCallbackProxy cb) {
} else if (cb != null) {
cb.onFailure();
}
paused = false;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ public void getScanner(Context ctx, final ProviderCallback cb, final ScannerSear
boolean compatible = true;

if (specificDevices != null && !specificDevices.isEmpty()) {
Log.d("IntentProvider", "Provider " + getKey() + ": Checking specific devices `" + specificDevices + "`");
boolean found = false;

for (String device : specificDevices) {
if (android.os.Build.MODEL.equals(device)) {
found = true;
Log.d("IntentProvider", "Provider " + getKey() + ": Specific devices `" + device + "` found");
break;
}
}
Expand All @@ -61,41 +63,54 @@ public void getScanner(Context ctx, final ProviderCallback cb, final ScannerSear
}

if (intentToTest != null) {
Log.d("IntentProvider", "Provider " + getKey() + ": Checking intent `" + intentToTest + "`");
if (!Common.checkIntentListener(intentToTest, ctx)) {
Log.d("IntentProvider", "Provider " + getKey() + ": Intent `" + intentToTest + "` not found");
compatible = false;
}
}

if (appPackageToTest != null) {
Log.d("IntentProvider", "Provider " + getKey() + ": Checking appPackage `" + appPackageToTest + "`");
PackageManager pkManager = ctx.getPackageManager();
try {
if (!pkManager.getApplicationInfo(appPackageToTest, 0).enabled) {
compatible = false;
Log.d("IntentProvider", "Provider " + getKey() + ": AppPackage `" + appPackageToTest + "` not found");
}
} catch (PackageManager.NameNotFoundException e) {
compatible = false;
Log.d("IntentProvider", "Provider " + getKey() + ": AppPackage `" + appPackageToTest + "` not found");
}
}

if (serviceToTest != null) {
Log.d("IntentProvider", "Provider " + getKey() + ": Checking service `" + serviceToTest + "`");
Intent i = new Intent();
i.setComponent(new ComponentName(serviceToTest.split("/")[0], serviceToTest.split("/")[1]));
try {
boolean success;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
ctx.startForegroundService(i);
success = ctx.startForegroundService(i) != null;
} else {
ctx.startService(i);
success = ctx.startService(i) != null;
}
if (!success) {
Log.d("IntentProvider", "Provider " + getKey() + ": Service `" + serviceToTest + "` not found");
compatible = false;
}
} catch (Exception e) {
Log.i("LaserScanner", "could not start service " + serviceToTest, e);
Log.d("IntentProvider", "Provider " + getKey() + ": Could not start service " + serviceToTest, e);
compatible = false;
}
}

if (compatible) {
Log.d("IntentProvider", "Provider " + getKey() + " compatible");
cb.onScannerCreated(getKey(), "internal", createNewScanner(ctx, options));
cb.onAllScannersCreated(getKey());
} else {
Log.d("IntentProvider", "Provider " + getKey() + " not compatible");
cb.onProviderUnavailable(getKey());
}
}
Expand Down
32 changes: 32 additions & 0 deletions enioka_scan_zebra_dw/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Zebra DataWedge provider

The goal of this plugin is to allow usage of Zebra devices that have the DataWedge management
service installed, which happens mostly on integrated Zebra devices (TC26, TC27, TC55...).

> Warning: there are multiple different providers that can manage those devices! Take care only to
> have the one you want enabled, using the include or exclude provider search parameters.
To use this provider:

* it needs to be in the class path (just add it to the Maven dependencies like
this: `implementation 'com.enioka.scanner:provider-cs-zebra-dw:x.y.z:aar'`)
* The DataWedge service must be installed on the device and running (enabled or disabled).

The plugin will on startup create or update a DataWedge profile that:

* is named after your application package name. The profile name can also be specified with a string resource named `enioka_scan_zebra_dw_profile_name`.
* is enabled. (note that the plugin will also enabled DataWedge itself if the service is running disabled)
* has scanning enabled
* has Intent output enabled, with:
* Intent name `com.enioka.scanners.zebra.dw.intent.callback.name` (this can be modified by
changing the string resource named `enioka_scan_zebra_dw_intent_name`)
* No Intent Category
* Intent Delivery set to `Broadcast Intent`

You are allowed to modify any other configuration option in the profile except those above, which
are reset on each initialization. This ensure you can tweak your scanning configuration as you want,
but are still guaranteed scanning will work on plugin startup.

This includes decoder configuration. The plugin will at runtime (and only at runtime, this is not
persisted in the profile) enable or disable the decoders selected inside your app, but all the other
numerous decoder parameters are free to configure inside the profile.
41 changes: 41 additions & 0 deletions enioka_scan_zebra_dw/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
apply plugin: 'scanner.published-library'

description 'Scanner provider for Zebra devices using the Zebra DataWedge service'

android {
compileSdkVersion 33
defaultConfig {
minSdkVersion 19
targetSdkVersion 28
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lint {
disable 'ExpiredTargetSdkVersion'
}
namespace 'com.enioka.scanner.sdk.zebra.dw'
}

ext {
mavenArtifactId = "provider-cs-zebra-dw"
}


dependencies {
// We need the APIs
implementation project(':enioka_scan')

// Linter helpers
implementation 'com.android.support:support-annotations:28.0.0'

// Test things (useless for now)
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testImplementation 'junit:junit:4.13.2'
}
Loading

0 comments on commit 7126f92

Please sign in to comment.