Skip to content

Commit 39ef3b7

Browse files
committed
AI Data Capture Demo changes for October release (AI Data Capture SDK release v3.1.4)
1.Release of AI Data Capture Demo App Version 1.0.10. 2.Upgraded Zebra Vision AISDK to version 3.1.4. 3.Modified OCR use cases to utilize the OCR Analyzer API supported by AISDK. 4.Enhanced the OCR Exact Match filter to allow searching multiple items using commas to separate them. 5.Introduced beep and haptic feedback for specific OCR filters. 6.Added a Speech-to-Text input feature for the OCR Exact Match filter. 7.Introduced new OCR filters—"Contains," "Starts With," and "Regex"—under the OCR Find use case. 8.Addressed camera limitation exceptions across various Zebra devices. 9.Extended support for additional Zebra devices, including MC34, MC94, ET6x, KC50, and HC5x.
1 parent 4be94e5 commit 39ef3b7

25 files changed

+2147
-1259
lines changed

AISuite_Demos/AIDataCaptureDemo/app/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ android {
1717
applicationId = "com.zebra.aidatacapturedemo"
1818
minSdk = 33
1919
targetSdk = 36
20-
versionCode = 7
20+
versionCode = 12
2121
val appVersion: String = libs.versions.appVersion.get().toString()
2222
versionName = appVersion
2323

@@ -95,6 +95,7 @@ dependencies {
9595
implementation(libs.camera.lifecycle)
9696
implementation(libs.camera.view)
9797
implementation(libs.androidx.camera.extensions)
98+
implementation(libs.runtime.permissions)
9899

99100
// JSON serialization
100101
implementation(libs.gson)

AISuite_Demos/AIDataCaptureDemo/app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
<uses-permission android:name="android.permission.INTERNET" />
99
<uses-permission android:name="android.permission.CAMERA" />
1010
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
11+
<uses-permission android:name="android.permission.VIBRATE"/>
12+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
1113

1214
<application
1315
android:allowBackup="true"

AISuite_Demos/AIDataCaptureDemo/app/src/main/java/com/zebra/aidatacapturedemo/MainActivity.kt

Lines changed: 21 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,47 @@ package com.zebra.aidatacapturedemo
22

33
import android.Manifest
44
import android.content.Context
5-
import android.content.pm.PackageManager
65
import android.os.Bundle
7-
import android.util.Log
86
import androidx.activity.ComponentActivity
97
import androidx.activity.compose.setContent
108
import androidx.activity.enableEdgeToEdge
11-
import androidx.activity.result.contract.ActivityResultContracts
129
import androidx.activity.viewModels
1310
import androidx.compose.foundation.layout.fillMaxSize
1411
import androidx.compose.material3.Scaffold
12+
import androidx.compose.runtime.LaunchedEffect
1513
import androidx.compose.ui.Modifier
16-
import androidx.core.content.ContextCompat
14+
import com.google.accompanist.permissions.ExperimentalPermissionsApi
15+
import com.google.accompanist.permissions.rememberMultiplePermissionsState
1716
import com.zebra.aidatacapturedemo.model.FileUtils
1817
import com.zebra.aidatacapturedemo.ui.theme.AIDataCaptureDemoTheme
1918
import com.zebra.aidatacapturedemo.ui.view.AIDataCaptureDemoApp
19+
import com.zebra.aidatacapturedemo.ui.view.FeedbackUtils
2020
import com.zebra.aidatacapturedemo.viewmodel.AIDataCaptureDemoViewModel
2121

22-
private const val TAG = "MainActivity"
23-
22+
@OptIn(ExperimentalPermissionsApi::class)
2423
class MainActivity : ComponentActivity() {
2524
override fun onCreate(savedInstanceState: Bundle?) {
2625
super.onCreate(savedInstanceState)
2726
enableEdgeToEdge()
28-
checkCameraPermissions()
29-
checkStoragePermissions()
3027

3128
FileUtils(application.filesDir.absolutePath, application as Context)
3229
val viewModel: AIDataCaptureDemoViewModel by viewModels { AIDataCaptureDemoViewModel.factory() }
30+
FeedbackUtils(viewModel, application as Context)
3331

3432
val activityLifecycle = lifecycle
3533
setContent {
34+
35+
val multiplePermissionsState = rememberMultiplePermissionsState(
36+
listOf(
37+
Manifest.permission.CAMERA,
38+
Manifest.permission.RECORD_AUDIO
39+
)
40+
)
41+
LaunchedEffect(Unit) {
42+
// Request the permission when the Composable first enters the composition
43+
multiplePermissionsState.launchMultiplePermissionRequest()
44+
}
45+
3646
AIDataCaptureDemoTheme {
3747
Scaffold(modifier = Modifier.fillMaxSize()) { activityInnerPadding ->
3848
AIDataCaptureDemoApp(viewModel, activityInnerPadding, activityLifecycle)
@@ -41,59 +51,8 @@ class MainActivity : ComponentActivity() {
4151
}
4252
}
4353

44-
/**
45-
* Check for camera permissions
46-
*/
47-
private fun checkCameraPermissions() {
48-
val cameraPermissionRequest =
49-
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
50-
if (isGranted) {
51-
Log.i(TAG, "Camera Permissions Granted")
52-
} else {
53-
Log.i(TAG, "Camera Permissions Not Granted")
54-
}
55-
56-
}
57-
58-
when (PackageManager.PERMISSION_GRANTED) {
59-
ContextCompat.checkSelfPermission(
60-
this,
61-
Manifest.permission.CAMERA
62-
) -> {
63-
Log.i(TAG, "Camera Permissions Already Granted")
64-
}
65-
66-
else -> {
67-
cameraPermissionRequest.launch(Manifest.permission.CAMERA)
68-
}
69-
}
70-
}
71-
72-
/**
73-
* Check for storage permissions
74-
*/
75-
private fun checkStoragePermissions() {
76-
val storagePermissionRequest =
77-
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
78-
if (isGranted) {
79-
Log.i(TAG, "Storage Permissions Granted")
80-
} else {
81-
Log.i(TAG, "Storage Permissions Not Granted")
82-
}
83-
84-
}
85-
86-
when (PackageManager.PERMISSION_GRANTED) {
87-
ContextCompat.checkSelfPermission(
88-
this,
89-
Manifest.permission.MANAGE_EXTERNAL_STORAGE
90-
) -> {
91-
Log.i(TAG, "Storage Permissions Already Granted")
92-
}
93-
94-
else -> {
95-
storagePermissionRequest.launch(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
96-
}
97-
}
54+
override fun onDestroy() {
55+
super.onDestroy()
56+
FeedbackUtils.deinitialize()
9857
}
9958
}

AISuite_Demos/AIDataCaptureDemo/app/src/main/java/com/zebra/aidatacapturedemo/data/AIDataCaptureDemoUiState.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ data class AIDataCaptureDemoUiState(
155155

156156
// Model --> UI
157157
var modelDemoReady: Boolean = false,
158+
var isCameraReady: Boolean = false,
159+
var cameraError: String? = null,
158160
var isProductEnrollmentCompleted: Boolean = false,
159161
var currentBitmap: Bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888),
160162
var captureBitmap: Bitmap? = null,
@@ -163,10 +165,5 @@ data class AIDataCaptureDemoUiState(
163165
val ocrResults: List<ResultData> = listOf(),
164166
var barcodeResults: List<ResultData> = listOf(),
165167

166-
// OCR Filter Settings
167-
var selectedOcrFilterType: OCRFilterType = OCRFilterType.SHOW_ALL,
168-
var selectedExactMatchString: String = "",
169-
var selectedNumericCharSliderValues: ClosedFloatingPointRange<Float> = (2f..15f),
170-
var selectedAlphaCharSliderValues: ClosedFloatingPointRange<Float> = (2f..15f),
171-
var selectedAlphaNumericCharSliderValues: ClosedFloatingPointRange<Float> = (2f..15f)
168+
var selectedOCRFilterData: OCRFilterData = OCRFilterData(ocrFilterType = OCRFilterType.SHOW_ALL)
172169
)
Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,40 @@
11
package com.zebra.aidatacapturedemo.data
22

33
/**
4-
* Available FilterType options
4+
* Available OCR FilterType options
55
*/
66
enum class OCRFilterType {
77
SHOW_ALL,
88
NUMERIC_CHARACTERS_ONLY,
99
ALPHA_CHARACTERS_ONLY,
1010
ALPHA_NUMERIC_CHARACTERS_ONLY,
11-
EXACT_MATCH
11+
EXACT_MATCH,
12+
STARTS_WITH,
13+
CONTAINS,
14+
REGEX
1215
}
16+
1317
/**
1418
* Kotlin data class used to communicate OCR Filter detailed information from View to @see OCRReader
1519
*
1620
* @property ocrFilterType - Available Filter Type option
1721
*
18-
* @property exactMatchString - Filter String used only when OCRFilterType.EXACT_MATCH is requested
22+
* @property exactMatchStringList - Filter String used only when OCRFilterType.EXACT_MATCH is requested
23+
*
24+
* @property numericCharLengthRange - Char Length Range value applicable for OCRFilterType.NUMERIC_CHARACTERS_ONLY
1925
*
20-
* @property charLengthMin - Filter String filtered Character minimum length.
21-
* This is applicable for all OCRFilterType except OCRFilterType.SHOW_ALL and OCRFilterType.EXACT_MATCH.
26+
* @property alphaCharLengthRange - Char Length Range value applicable for OCRFilterType.ALPHA_CHARACTERS_ONLY
2227
*
23-
* @property charLengthMax - Filter String filtered Character maximum length.
24-
* This is applicable for all OCRFilterType except OCRFilterType.SHOW_ALL and OCRFilterType.EXACT_MATCH.
28+
* @property alphaNumericCharLengthRange - Char Length Range value applicable for OCRFilterType.ALPHA_NUMERIC_CHARACTERS_ONLY
2529
*
2630
*/
2731
data class OCRFilterData(
2832
val ocrFilterType: OCRFilterType,
29-
val exactMatchString: String? = null,
30-
val charLengthMin: Int? = null,
31-
val charLengthMax: Int? = null
33+
val exactMatchStringList: List<String> = listOf(),
34+
val startsWithString: String = "",
35+
val containsString: String = "",
36+
val regexString: String = "",
37+
var numericCharLengthRange: ClosedFloatingPointRange<Float> = (2f..15f),
38+
var alphaCharLengthRange: ClosedFloatingPointRange<Float> = (2f..15f),
39+
var alphaNumericCharLengthRange: ClosedFloatingPointRange<Float> = (2f..15f)
3240
)

AISuite_Demos/AIDataCaptureDemo/app/src/main/java/com/zebra/aidatacapturedemo/data/ProductData.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import com.zebra.ai.vision.detector.Recognizer.Recognition
1313
* @param bBox: BBox
1414
* @param crop: Bitmap
1515
*/
16-
class ProductData(var point: Point, var text: String, var bBox: BBox, var crop : Bitmap) {}
16+
class ProductData(var text: String, var bBox: BBox, var crop : Bitmap)
1717

1818
/**
1919
* toProductData function used to convert input bitmap, products and recognitions to product data
@@ -29,7 +29,6 @@ fun toProductData(inputBitmap:Bitmap, products: Array<BBox>, recognitions: Array
2929
(products[i].ymin.toInt() + (products[i].ymax - products[i].ymin).toInt() < inputBitmap.height)) {
3030
if (recognitions[i].similarity.first() > 0.80) {
3131
ProductData += ProductData(
32-
Point(products[i].xmin.toInt(), products[i].ymin.toInt()),
3332
recognitions[i].sku.first(),
3433
products[i],
3534
Bitmap.createBitmap(
@@ -42,7 +41,6 @@ fun toProductData(inputBitmap:Bitmap, products: Array<BBox>, recognitions: Array
4241
)
4342
} else {
4443
ProductData += ProductData(
45-
Point(products[i].xmin.toInt(), products[i].ymin.toInt()),
4644
"",
4745
products[i],
4846
Bitmap.createBitmap(

AISuite_Demos/AIDataCaptureDemo/app/src/main/java/com/zebra/aidatacapturedemo/model/BarcodeEntityTrackerAnalyzer.kt renamed to AISuite_Demos/AIDataCaptureDemo/app/src/main/java/com/zebra/aidatacapturedemo/model/BarcodeAnalyzer.kt

Lines changed: 12 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,35 @@
33
package com.zebra.aidatacapturedemo.model
44

55
import android.util.Log
6-
import androidx.camera.core.ImageAnalysis
76
import androidx.lifecycle.Lifecycle
8-
import androidx.lifecycle.coroutineScope
9-
import com.zebra.ai.vision.analyzer.tracking.EntityTrackerAnalyzer
10-
import com.zebra.ai.vision.detector.AIVisionSDKException
117
import com.zebra.ai.vision.detector.BarcodeDecoder
12-
import com.zebra.ai.vision.entity.BarcodeEntity
138
import com.zebra.aidatacapturedemo.data.AIDataCaptureDemoUiState
14-
import com.zebra.aidatacapturedemo.data.ResultData
159
import com.zebra.aidatacapturedemo.data.PROFILING
1610
import com.zebra.aidatacapturedemo.viewmodel.AIDataCaptureDemoViewModel
17-
import kotlinx.coroutines.Dispatchers
1811
import kotlinx.coroutines.flow.StateFlow
19-
import kotlinx.coroutines.launch
2012
import java.io.IOException
2113
import java.util.concurrent.ExecutorService
2214
import java.util.concurrent.Executors
2315

2416

2517
/**
26-
* [BarcodeEntityTrackerAnalyzer] class is used to detect & Track barcodes found on the Camera Live Preview
18+
* [BarcodeAnalyzer] class is used to detect & Track barcodes found on the Camera Live Preview
2719
*
2820
* @param uiState - Used to read all the UI Current State
2921
* @param viewModel - Used to write any UI State Changes via [AIDataCaptureDemoViewModel]
3022
*/
31-
class BarcodeEntityTrackerAnalyzer(
23+
class BarcodeAnalyzer(
3224
val uiState: StateFlow<AIDataCaptureDemoUiState>,
33-
val viewModel: AIDataCaptureDemoViewModel
34-
) {
25+
val viewModel: AIDataCaptureDemoViewModel) {
3526

3627
private lateinit var mActivityLifecycle: Lifecycle
37-
private val TAG = "BarcodeEntityTrackerAnalyzer"
28+
private val TAG = "BarcodeAnalyzer"
3829
private var barcodeDecoder: BarcodeDecoder? = null
3930
private val decoderSettings = BarcodeDecoder.Settings("barcode-localizer")
4031
private val executorService: ExecutorService = Executors.newSingleThreadExecutor()
4132

4233
/**
43-
* To initialize the BarcodeEntityTrackerAnalyzer
34+
* To initialize the BarcodeAnalyzer
4435
*/
4536
fun initialize() {
4637
barcodeDecoder?.dispose()
@@ -56,11 +47,11 @@ class BarcodeEntityTrackerAnalyzer(
5647
updateModelDemoReady(true)
5748
Log.e(
5849
PROFILING,
59-
"BarcodeEntityTrackerAnalyzer obj creation / model loading time = ${System.currentTimeMillis() - mStart} milli sec"
50+
"BarcodeAnalyzer obj creation / model loading time = ${System.currentTimeMillis() - mStart} milli sec"
6051
)
61-
Log.i(TAG, "BarcodeEntityTrackerAnalyzer init Success")
52+
Log.i(TAG, "BarcodeAnalyzer init Success")
6253
}.exceptionally { e: Throwable ->
63-
Log.e(TAG, "BarcodeEntityTrackerAnalyzer init Failed -> " + e.message)
54+
Log.e(TAG, "BarcodeAnalyzer init Failed -> " + e.message)
6455
if (e.message?.contains("Given runtimes are not available") == true ||
6556
e.message?.contains("Initialize barcodeDecoder due to SNPE exception") == true
6657
) {
@@ -76,27 +67,16 @@ class BarcodeEntityTrackerAnalyzer(
7667
}
7768
}
7869

79-
fun setupEntityTrackerAnalyzer(myLifecycle: Lifecycle): EntityTrackerAnalyzer {
80-
mActivityLifecycle = myLifecycle
81-
82-
val entityTrackerAnalyzer = EntityTrackerAnalyzer(
83-
listOf(barcodeDecoder),
84-
ImageAnalysis.COORDINATE_SYSTEM_ORIGINAL,
85-
executorService,
86-
::handleEntities
87-
)
88-
89-
return entityTrackerAnalyzer
90-
}
91-
9270
/**
93-
* To deinitialize the BarcodeEntityTrackerAnalyzer, we need to dispose the localizer
71+
* To deinitialize the BarcodeAnalyzer, we need to dispose the localizer
9472
*/
9573
fun deinitialize() {
9674
barcodeDecoder?.dispose()
9775
barcodeDecoder = null
9876
}
99-
77+
fun getDetector() : BarcodeDecoder? {
78+
return barcodeDecoder
79+
}
10080
private fun configure() {
10181
try {
10282
//Swap the values as the presented index is reverse of what model expects
@@ -167,24 +147,6 @@ class BarcodeEntityTrackerAnalyzer(
167147
}
168148
}
169149

170-
private fun handleEntities(result: EntityTrackerAnalyzer.Result) {
171-
mActivityLifecycle.coroutineScope.launch(Dispatchers.Main) {
172-
barcodeDecoder?.let {
173-
val returnEntityList = result.getValue(it)
174-
var rectList: List<ResultData> = mutableListOf()
175-
returnEntityList?.forEach { entity ->
176-
if (entity != null) {
177-
val barcodeEntity = entity as BarcodeEntity
178-
val value = barcodeEntity.value
179-
val rect = barcodeEntity.boundingBox
180-
rectList += ResultData(boundingBox = rect, text = value)
181-
}
182-
}
183-
viewModel.updateBarcodeResultData(results = rectList)
184-
}
185-
}
186-
}
187-
188150
private fun updateModelDemoReady(isReady: Boolean) {
189151
viewModel.updateModelDemoReady(isReady = isReady)
190152
}

AISuite_Demos/AIDataCaptureDemo/app/src/main/java/com/zebra/aidatacapturedemo/model/FileUtils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ class FileUtils(cacheDir: String, context : Context) {
305305
if(input != null) {
306306
val bitmap = BitmapFactory.decodeStream(input)
307307
file.parentFile?.name?.let {
308-
listOfProductData += ProductData(Point(0, 0), it, BBox(), bitmap)
308+
listOfProductData += ProductData( it, BBox(), bitmap)
309309
}
310310
input.close()
311311
}

0 commit comments

Comments
 (0)