diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8ed48cc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Calin Daniel Neamtu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2b25aa --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# DataWedge Picklist OCR Demo + +Demo Application demonstrating the integration and the usage of the PickList OCR feature.
+It includes code logic for: +- Profile creation +- PickList OCR enablement +- Parsing response and reconstruction of captured image + +## Blog Post + +This demo is part of a blog post which has been released on the Zebra Developer Portal where I'm explaining parts of this code step by step. +If you're interested, feel free to head over [here](https://developer.zebra.com/blog/integrating-datawedges-picklist-ocr-your-app). + +## Disclaimer + +Please be aware that this application is distributed as is without any guarantee of additional support or updates. \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..c4229d6 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,56 @@ +plugins { + alias(libs.plugins.androidApplication) + alias(libs.plugins.jetbrainsKotlinAndroid) +} + +android { + namespace = "com.bruvland.carphototaker2000" + compileSdk = 34 + + defaultConfig { + applicationId = "com.bruvland.carphototaker2000" + minSdk = 30 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = "1.8" + } + + buildFeatures { + viewBinding = true + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.lifecycle.livedata.ktx) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.navigation.ui.ktx) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/zebra/nilac/carphototaker2000/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/zebra/nilac/carphototaker2000/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..404b2c6 --- /dev/null +++ b/app/src/androidTest/java/com/zebra/nilac/carphototaker2000/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.zebra.nilac.carphototaker2000 + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.bruvland.carphototaker2000", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..09de968 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/bruvland/carphototaker2000/MainActivity.kt b/app/src/main/java/com/bruvland/carphototaker2000/MainActivity.kt new file mode 100644 index 0000000..593ea76 --- /dev/null +++ b/app/src/main/java/com/bruvland/carphototaker2000/MainActivity.kt @@ -0,0 +1,137 @@ +package com.bruvland.carphototaker2000 + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Build +import android.os.Bundle +import android.util.Log +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import com.bruvland.carphototaker2000.databinding.ActivityMainBinding +import com.bruvland.carphototaker2000.util.AppConstants +import com.bruvland.carphototaker2000.util.DWUtil +import org.json.JSONArray + +class MainActivity : AppCompatActivity() { + + private lateinit var binding: ActivityMainBinding + private lateinit var resultsAdapter: ResultsAdapter + + private val mainViewModel: MainViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + setSupportActionBar(binding.toolbar) + registerReceivers() + setUpAdapter() + + binding.scanButton.setOnClickListener { + launchScanningSession() + } + + mainViewModel.processedOutputResult.observe(this, processedResultObserver) + + //Create DW Profile if it doesn't exist already + sendBroadcast(DWUtil.generateDWBaseProfile(this)) + } + + private fun registerReceivers() { + val filter = IntentFilter() + filter.addAction("com.symbol.datawedge.api.RESULT_ACTION") + filter.addAction(AppConstants.DW_SCANNER_INTENT_ACTION) + filter.addCategory("android.intent.category.DEFAULT") + registerReceiver(dwReceiver, filter) + } + + private fun setUpAdapter() { + resultsAdapter = ResultsAdapter() + binding.resultsList.apply { + layoutManager = LinearLayoutManager(this@MainActivity) + itemAnimator = DefaultItemAnimator() + adapter = resultsAdapter + } + } + + private fun launchScanningSession() { + sendBroadcast(Intent().apply { + setPackage(AppConstants.DATAWEDGE_PACKAGE) + setAction(AppConstants.DATAWEDGE_API_ACTION) + putExtra(AppConstants.EXTRA_SOFT_SCAN_TRIGGER, "TOGGLE_SCANNING") + }) + } + + private val dwReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val action = intent.action + val extras = intent.extras + var resultInfo = "" + + if (extras != null && intent.hasExtra("RESULT_LIST")) { + if (extras.getString(AppConstants.COMMAND_IDENTIFIER_EXTRA) + .equals(AppConstants.PROFILE_CREATION_COMMAND_IDENTIFIER) + ) { + val resultList: ArrayList = + extras.get("RESULT_LIST") as ArrayList + + if (resultList.size > 0) { + var allSuccess = true + + // Iterate through the result list for each module + for (result in resultList) { + val module = result.getString("MODULE") + val resultCode = result.getString("RESULT_CODE") + val subResultCode = result.getString("SUB_RESULT_CODE") + + if (result.getString("RESULT").equals("FAILURE") + && !module.equals("APP_LIST") + ) { + // Profile creation failed for the module. + // Getting more information on what failed + allSuccess = false + + resultInfo = "Module: $module\n" // Name of the module that failed + resultInfo += "Result code: $resultCode\n" // Information on the type of the failure + if (!subResultCode.isNullOrEmpty()) // More Information on the failure if exists + resultInfo += "\tSub Result code: $subResultCode\n" + break + } else { + // Profile creation success for the module. + resultInfo = "Module: " + result.getString("MODULE") + "\n" + resultInfo += "Result: " + result.getString("RESULT") + "\n" + } + } + if (allSuccess) { + Log.d(TAG, "Profile created successfully") + binding.scanButton.isEnabled = true + } else { + Log.e(TAG, "Profile creation failed!\n\n$resultInfo") + } + } + } + } else if (extras != null && + action.equals(AppConstants.DW_SCANNER_INTENT_ACTION, ignoreCase = true) + ) { + val jsonData: String = extras.getString(AppConstants.DATA_TAG)!! + mainViewModel.parseOCRResult(jsonData) + } + } + } + + private val processedResultObserver: Observer = + Observer { result -> + resultsAdapter.notifyAdapter(result) + binding.resultsList.scrollToPosition(0) + } + + companion object { + const val TAG = "MainActivity" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bruvland/carphototaker2000/MainViewModel.kt b/app/src/main/java/com/bruvland/carphototaker2000/MainViewModel.kt new file mode 100644 index 0000000..760f8c5 --- /dev/null +++ b/app/src/main/java/com/bruvland/carphototaker2000/MainViewModel.kt @@ -0,0 +1,128 @@ +package com.bruvland.carphototaker2000 + +import java.text.SimpleDateFormat +import java.util.Locale + +import android.app.Application +import android.content.ContentResolver +import android.database.Cursor +import android.graphics.Bitmap +import android.net.Uri +import android.util.Log +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.bruvland.carphototaker2000.util.AppConstants +import com.bruvland.carphototaker2000.util.DWUtil +import com.bruvland.carphototaker2000.util.ImageProcessing +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.json.JSONArray +import org.json.JSONObject +import java.io.ByteArrayOutputStream +import java.util.Date + +class MainViewModel(private var application: Application) : AndroidViewModel(application) { + + val processedOutputResult: MutableLiveData by lazy { + MutableLiveData() + } + + fun parseOCRResult(json: String) { + viewModelScope.launch(Dispatchers.IO) { + println(json) + val jsonArray = JSONArray(json) + val jsonObject = jsonArray.getJSONObject(0) + + val uri = if (jsonObject.has(AppConstants.KEY_STRING_URI)) { + jsonObject.getString("uri") + } else { + "" + } + + // + //if (uri.isEmpty() || jsonArray.length() == 1) { + + // processedOutputResult.postValue( + // OutputResult( + // DWUtil.extractStringDataFromJson(jsonArray[0] as JSONObject), Date(), null + // ) + // ) + // return@launch + //} + + //Extract image from provided URI + val baos = ByteArrayOutputStream() + var nextURI: String? = uri + + val contentResolver: ContentResolver = application.contentResolver + + // Loop to collect all the data from the URIs + while (!nextURI.isNullOrEmpty()) { + val cursor = contentResolver.query(Uri.parse(nextURI), null, null, null, null) + cursor?.use { + nextURI = if (it.moveToFirst()) { + val rawData = it.getBlob(it.getColumnIndex(AppConstants.RAW_DATA)) + baos.write(rawData) + it.getString(it.getColumnIndex(AppConstants.DATA_NEXT_URI)) + } else { + null + } + } + } + + // Extract image data from the JSON object + val width = jsonObject.getInt(AppConstants.IMAGE_WIDTH) + val height = jsonObject.getInt(AppConstants.IMAGE_HEIGHT) + val stride = jsonObject.getInt(AppConstants.STRIDE) + val orientation = jsonObject.getInt(AppConstants.ORIENTATION) + val imageFormat = jsonObject.getString(AppConstants.IMAGE_FORMAT) + + // Decode the image + val bitmap: Bitmap = ImageProcessing.getInstance().getBitmap( + baos.toByteArray(), imageFormat, orientation, stride, width, height + ) + + //save photo to file + val timestamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + + val photoName = "car-photo_${timestamp}.jpg"; + + val contentValues = android.content.ContentValues(); + contentValues.put(android.provider.MediaStore.MediaColumns.RELATIVE_PATH, "Pictures/OCR") + contentValues.put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, photoName) + contentValues.put(android.provider.MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") + + val appthing = application.contentResolver; + val imageUri = appthing.insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) + if (imageUri != null) { + val outputStream = appthing.openOutputStream(imageUri) + if (outputStream != null) { + try { + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) + Log.d(TAG, "Image saved to Photos: $imageUri") + } finally { + outputStream.close() + } + } else { + Log.e(TAG, "Failed to open output stream for image URI") + } + } else { + Log.e(TAG, "Failed to insert image into MediaStore") + } + + + + processedOutputResult.postValue( + OutputResult( + Date(), bitmap + ) + ) + } + } + + companion object { + const val TAG = "MainViewModel" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bruvland/carphototaker2000/OutputResult.kt b/app/src/main/java/com/bruvland/carphototaker2000/OutputResult.kt new file mode 100644 index 0000000..8f1fdea --- /dev/null +++ b/app/src/main/java/com/bruvland/carphototaker2000/OutputResult.kt @@ -0,0 +1,11 @@ +package com.bruvland.carphototaker2000 + +import android.graphics.Bitmap +import java.util.Date + +data class OutputResult( + + var date: Date, + + var image: Bitmap? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/bruvland/carphototaker2000/ResultsAdapter.kt b/app/src/main/java/com/bruvland/carphototaker2000/ResultsAdapter.kt new file mode 100644 index 0000000..d15482c --- /dev/null +++ b/app/src/main/java/com/bruvland/carphototaker2000/ResultsAdapter.kt @@ -0,0 +1,66 @@ +package com.bruvland.carphototaker2000 + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.bruvland.carphototaker2000.databinding.ResultRowBinding +import java.text.SimpleDateFormat +import java.util.Locale + +class ResultsAdapter : RecyclerView.Adapter() { + + private val dateOutputFormatter = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.getDefault()) + + private lateinit var mInflater: LayoutInflater + private lateinit var mContext: Context + + private var mResultsList: MutableList = ArrayList() + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): ViewHolder { + mContext = parent.context + mInflater = LayoutInflater.from(mContext) + + val view = ResultRowBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val result = mResultsList[position] + + holder.mBinder.date.text = dateOutputFormatter.format(result.date) + + if (result.image != null) { + holder.mBinder.capturedImage.apply { + visibility = View.VISIBLE + setImageBitmap(result.image) + } + } else { + View.GONE + } + } + + override fun getItemCount(): Int { + return mResultsList.size + } + + fun notifyAdapter(item: OutputResult) { + mResultsList.add(0, item) + notifyItemInserted(0) + } + + class ViewHolder internal constructor(binding: ResultRowBinding) : + RecyclerView.ViewHolder(binding.root) { + + var mBinder = binding + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bruvland/carphototaker2000/util/AppConstants.kt b/app/src/main/java/com/bruvland/carphototaker2000/util/AppConstants.kt new file mode 100644 index 0000000..19f26f1 --- /dev/null +++ b/app/src/main/java/com/bruvland/carphototaker2000/util/AppConstants.kt @@ -0,0 +1,29 @@ +package com.bruvland.carphototaker2000.util +object AppConstants { + + const val WORKFLOW_MODE = "freeform_image_capture" // or document_capture + const val PROFILE_NAME = "ORC img capture" + + const val DATAWEDGE_PACKAGE = "com.symbol.datawedge" + + const val EXTRA_SOFT_SCAN_TRIGGER = "com.symbol.datawedge.api.SOFT_SCAN_TRIGGER" + + const val DATAWEDGE_API_ACTION = "com.symbol.datawedge.api.ACTION" + const val DW_SCANNER_INTENT_ACTION = "com.zebra.nilac.dwpicklistocrdemo.SCANNER" + + const val KEY_STRING_DATA = "string_data" + const val KEY_STRING_URI = "uri" + + const val DATA_NEXT_URI = "next_data_uri" + const val STRIDE = "stride" + const val RAW_DATA = "raw_data" + const val ORIENTATION = "orientation" + const val IMAGE_FORMAT = "imageformat" + const val IMAGE_WIDTH = "width" + const val IMAGE_HEIGHT = "height" + + const val DATA_TAG = "com.symbol.datawedge.data" + + const val COMMAND_IDENTIFIER_EXTRA = "COMMAND_IDENTIFIER" + const val PROFILE_CREATION_COMMAND_IDENTIFIER = "CREATE_PROFILE" +} \ No newline at end of file diff --git a/app/src/main/java/com/bruvland/carphototaker2000/util/DWUtil.kt b/app/src/main/java/com/bruvland/carphototaker2000/util/DWUtil.kt new file mode 100644 index 0000000..6e79f24 --- /dev/null +++ b/app/src/main/java/com/bruvland/carphototaker2000/util/DWUtil.kt @@ -0,0 +1,211 @@ +package com.bruvland.carphototaker2000.util + +import android.content.Context +import android.content.Intent +import android.database.Cursor +import android.graphics.Bitmap +import android.net.Uri +import android.os.Bundle +import android.util.Log +import android.widget.ImageView +import com.bruvland.carphototaker2000.OutputResult +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.io.ByteArrayOutputStream +import java.io.IOException + +object DWUtil { + + private const val TAG = "DWUtil" + + fun generateDWBaseProfile(context: Context): Intent { + val bMain = Bundle().apply { + putString("PROFILE_NAME", AppConstants.PROFILE_NAME) + putString("PROFILE_ENABLED", "true") + putString("CONFIG_MODE", "CREATE_IF_NOT_EXIST") + putString("RESET_CONFIG", "true") + } + + val configApplicationList = Bundle().apply { + putString("PACKAGE_NAME", context.packageName) + putStringArray("ACTIVITY_LIST", arrayOf("*")) + } + + val intentModuleParamList = Bundle().apply { + putString("intent_output_enabled", "true") + putString("intent_action", AppConstants.DW_SCANNER_INTENT_ACTION) + putInt("intent_delivery", 2) + } + + val intentModule = Bundle().apply { + putString("PLUGIN_NAME", "INTENT") + putString("RESET_CONFIG", "true") + putBundle("PARAM_LIST", intentModuleParamList) + } + + val keystrokeModuleParamList = Bundle().apply { + putString("keystroke_output_enabled", "false") + } + + val keystrokeModule = Bundle().apply { + putString("PLUGIN_NAME", "KEYSTROKE") + putString("RESET_CONFIG", "true") + putBundle("PARAM_LIST", keystrokeModuleParamList) + } + + bMain.putParcelableArrayList( + "PLUGIN_CONFIG", arrayListOf( + intentModule, + keystrokeModule, + //enablePickListOCR() + ) + ) + bMain.putParcelableArray("APP_LIST", arrayOf(configApplicationList)) + + return Intent().apply { + action = "com.symbol.datawedge.api.ACTION" + setPackage("com.symbol.datawedge") + putExtra("com.symbol.datawedge.api.SET_CONFIG", bMain) + putExtra("SEND_RESULT", "COMPLETE_RESULT") + putExtra("COMMAND_IDENTIFIER", AppConstants.PROFILE_CREATION_COMMAND_IDENTIFIER) + } + } + + private fun enablePickListOCR(): Bundle { + val bPickListOcr = Bundle().apply { + putString("module", "MlKitExModule") + putBundle("module_params", Bundle().apply { + putString("session_timeout", "3000") //Integer Range 0 – 60000 + putString("illumination", "off") //on - off + putString("output_image", "2") // 0 - Disabled, 2 - Cropped Image + putString( + "script", + "0" + ) // 0 - Latin, 1 - Latin & Chinese, 2 - Latin and Japanese, 3 - Latin and Korean, 4 Latin and Devanagari + putString("confidence_level", "70") // Integer range 0-100 + putString("text_structure", "0") // 0 - Single Word, 1- Single Line + putString( + "picklist_mode", + "0" + ) // 0 - OCR or Barcode, 1 - OCR Only, 2 - Barcode Only + + putParcelableArrayList("rules", + arrayListOf( + Bundle().apply { + putParcelableArrayList("rule_list", createOCRRules()) + putString("rule_param_id", "report_ocr_data") + } + ) + ) + }) + } + val bPickListBarcode = Bundle().apply { + putString("module", "BarcodeDecoderModule") + putBundle("module_params", Bundle().apply { + putParcelableArrayList("rules", + arrayListOf( + Bundle().apply { + putParcelableArrayList("rule_list", createBarcodeRules()) + putString("rule_param_id", "report_barcode_data") + } + ) + ) + }) + } + + val bConfigWorkflowParamList = Bundle().apply { + putString("workflow_name", "picklist_ocr") + putString("workflow_input_source", "2") + putParcelableArrayList("workflow_params", arrayListOf(bPickListOcr, bPickListBarcode)) + } + + val bConfigWorkflow = Bundle().apply { + putString("PLUGIN_NAME", "WORKFLOW") + putString("RESET_CONFIG", "true") + + putString("workflow_input_enabled", "true") + putString("selected_workflow_name", "picklist_ocr") + putString("workflow_input_source", "2") //1 - Imager 2 - Camera + + putParcelableArrayList("PARAM_LIST", arrayListOf(bConfigWorkflowParamList)) + } + + Log.i(TAG, "Creating DW Profile unless it doesn't exists already") + return bConfigWorkflow + } + + private fun createBarcodeRules(): ArrayList { + val ean8Rule = Bundle().apply { + putString("rule_name", "EAN8") + putBundle("criteria", Bundle().apply { + putParcelableArrayList( + "identifier", arrayListOf( + Bundle().apply { + putString("criteria_key", "starts_with") + putString("criteria_value", "58") + } + )) + putStringArray("symbology", arrayOf("decoder_ean8")) + }) + putParcelableArrayList("actions", arrayListOf( + Bundle().apply { + putString("action_key", "report") + putString("action_value", "") + } + )) + } + return arrayListOf(ean8Rule) + } + + private fun createOCRRules(): ArrayList { + val testOcrRule = Bundle().apply { + putString("rule_name", "TestOCR") + putBundle("criteria", Bundle().apply { + putParcelableArrayList( + "identifier", arrayListOf( + Bundle().apply { + putString("criteria_key", "min_length") + putString("criteria_value", "3") + }, + Bundle().apply { + putString("criteria_key", "max_length") + putString("criteria_value", "7") + }, + Bundle().apply { + putString("criteria_key", "starts_with") + putString("criteria_value", "A") + }, + + Bundle().apply { + putString("criteria_key", "contains") + putString("criteria_value", "BA") + }, + + Bundle().apply { + putString("criteria_key", "ignore_case") + putString("criteria_value", "true") + }) + ) + }) + putParcelableArrayList("actions", arrayListOf( + Bundle().apply { + putString("action_key", "report") + putString("action_value", "") + } + )) + } + return arrayListOf(testOcrRule) + } + + + fun extractStringDataFromJson(jsonObject: JSONObject): String { + val stringData = jsonObject.get(AppConstants.KEY_STRING_DATA).toString() + Log.d(TAG, "New captured Data: $stringData") + + return stringData + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bruvland/carphototaker2000/util/ImageProcessing.java b/app/src/main/java/com/bruvland/carphototaker2000/util/ImageProcessing.java new file mode 100644 index 0000000..ebca9b5 --- /dev/null +++ b/app/src/main/java/com/bruvland/carphototaker2000/util/ImageProcessing.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018-2023 Zebra Technologies Corp + * All rights reserved. + */ +package com.bruvland.carphototaker2000.util; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.ImageFormat; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.YuvImage; + +import java.io.ByteArrayOutputStream; + +public class ImageProcessing { + + private final String IMG_FORMAT_YUV = "YUV"; + private final String IMG_FORMAT_Y8 = "Y8"; + + private static ImageProcessing instance = null; + + public static ImageProcessing getInstance() { + + if (instance == null) { + instance = new ImageProcessing(); + } + return instance; + } + + private ImageProcessing() { + //Private Constructor + } + + public Bitmap getBitmap(byte[] data, String imageFormat, int orientation, int stride, int width, int height) { + if (imageFormat.equalsIgnoreCase(IMG_FORMAT_YUV)) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, width, height, new int[]{stride, stride}); + yuvImage.compressToJpeg(new Rect(0, 0, stride, height), 100, out); + yuvImage.getYuvData(); + byte[] imageBytes = out.toByteArray(); + if (orientation != 0) { + Matrix matrix = new Matrix(); + matrix.postRotate(orientation); + Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length); + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + } else { + return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length); + } + } else if (imageFormat.equalsIgnoreCase(IMG_FORMAT_Y8)) { + return convertYtoJPG_CPU(data, orientation, stride, height); + } + + return null; + } + + + private Bitmap convertYtoJPG_CPU(byte[] data, int orientation, int stride, int height) { + int mLength = data.length; + int[] pixels = new int[mLength]; + for (int i = 0; i < mLength; i++) { + int p = data[i] & 0xFF; + pixels[i] = 0xff000000 | p << 16 | p << 8 | p; + } + if (orientation != 0) { + Matrix matrix = new Matrix(); + matrix.postRotate(orientation); + Bitmap bitmap = Bitmap.createBitmap(pixels, stride, height, Bitmap.Config.ARGB_8888); + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + } else { + return Bitmap.createBitmap(pixels, stride, height, Bitmap.Config.ARGB_8888); + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_generic_scan.xml b/app/src/main/res/drawable/ic_generic_scan.xml new file mode 100644 index 0000000..c5e06dc --- /dev/null +++ b/app/src/main/res/drawable/ic_generic_scan.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..7ab5071 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/result_row.xml b/app/src/main/res/layout/result_row.xml new file mode 100644 index 0000000..b849e91 --- /dev/null +++ b/app/src/main/res/layout/result_row.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 0000000..c2d8462 --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,143 @@ + + #A1D39A + #0A390F + #245024 + #BCF0B4 + #BACCB3 + #253423 + #3B4B38 + #D5E8CE + #A0CFD4 + #00363B + #1F4D52 + #BCEBF0 + #FFB4AB + #690005 + #93000A + #FFDAD6 + #10140F + #E0E4DB + #10140F + #E0E4DB + #424940 + #C2C9BD + #8C9388 + #424940 + #000000 + #E0E4DB + #2D322C + #3B6939 + #BCF0B4 + #002204 + #A1D39A + #245024 + #D5E8CE + #111F0F + #BACCB3 + #3B4B38 + #BCEBF0 + #002023 + #A0CFD4 + #1F4D52 + #10140F + #363A34 + #0B0F0A + #191D17 + #1D211B + #272B25 + #323630 + #A5D89E + #001C03 + #6D9C68 + #000000 + #BED0B7 + #0B1A0A + #84967F + #000000 + #A5D3D8 + #001A1C + #6B989D + #000000 + #FFBAB1 + #370001 + #FF5449 + #000000 + #10140F + #E0E4DB + #10140F + #F9FCF3 + #424940 + #C6CDC1 + #9EA59A + #7E857B + #000000 + #E0E4DB + #272B25 + #255125 + #BCF0B4 + #001602 + #A1D39A + #113F14 + #D5E8CE + #071406 + #BACCB3 + #2B3A28 + #BCEBF0 + #001416 + #A0CFD4 + #073C41 + #10140F + #363A34 + #0B0F0A + #191D17 + #1D211B + #272B25 + #323630 + #F1FFEA + #000000 + #A5D89E + #000000 + #F1FFEA + #000000 + #BED0B7 + #000000 + #EFFDFF + #000000 + #A5D3D8 + #000000 + #FFF9F9 + #000000 + #FFBAB1 + #000000 + #10140F + #E0E4DB + #10140F + #FFFFFF + #424940 + #F7FDF0 + #C6CDC1 + #C6CDC1 + #000000 + #E0E4DB + #000000 + #033209 + #C0F4B8 + #000000 + #A5D89E + #001C03 + #DAECD3 + #000000 + #BED0B7 + #0B1A0A + #C0EFF5 + #000000 + #A5D3D8 + #001A1C + #10140F + #363A34 + #0B0F0A + #191D17 + #1D211B + #272B25 + #323630 + diff --git a/app/src/main/res/values-night/theme_overlays.xml b/app/src/main/res/values-night/theme_overlays.xml new file mode 100644 index 0000000..02adac7 --- /dev/null +++ b/app/src/main/res/values-night/theme_overlays.xml @@ -0,0 +1,98 @@ + + + + diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..9514ee4 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,60 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..e9150f9 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,144 @@ + + + #3B6939 + #FFFFFF + #BCF0B4 + #002204 + #52634F + #FFFFFF + #D5E8CE + #111F0F + #38656A + #FFFFFF + #BCEBF0 + #002023 + #BA1A1A + #FFFFFF + #FFDAD6 + #410002 + #F7FBF1 + #191D17 + #F7FBF1 + #191D17 + #DEE5D8 + #424940 + #72796F + #C2C9BD + #000000 + #2D322C + #EFF2E9 + #A1D39A + #BCF0B4 + #002204 + #A1D39A + #245024 + #D5E8CE + #111F0F + #BACCB3 + #3B4B38 + #BCEBF0 + #002023 + #A0CFD4 + #1F4D52 + #D8DBD2 + #F7FBF1 + #FFFFFF + #F1F5EB + #ECEFE6 + #E6E9E0 + #E0E4DB + #204C20 + #FFFFFF + #517F4E + #FFFFFF + #374734 + #FFFFFF + #687964 + #FFFFFF + #1A494E + #FFFFFF + #4F7C81 + #FFFFFF + #8C0009 + #FFFFFF + #DA342E + #FFFFFF + #F7FBF1 + #191D17 + #F7FBF1 + #191D17 + #DEE5D8 + #3E453C + #5A6157 + #767D72 + #000000 + #2D322C + #EFF2E9 + #A1D39A + #517F4E + #FFFFFF + #396637 + #FFFFFF + #687964 + #FFFFFF + #50604C + #FFFFFF + #4F7C81 + #FFFFFF + #366368 + #FFFFFF + #D8DBD2 + #F7FBF1 + #FFFFFF + #F1F5EB + #ECEFE6 + #E6E9E0 + #E0E4DB + #002905 + #FFFFFF + #204C20 + #FFFFFF + #172616 + #FFFFFF + #374734 + #FFFFFF + #00272A + #FFFFFF + #1A494E + #FFFFFF + #4E0002 + #FFFFFF + #8C0009 + #FFFFFF + #F7FBF1 + #191D17 + #F7FBF1 + #000000 + #DEE5D8 + #1F261E + #3E453C + #3E453C + #000000 + #2D322C + #FFFFFF + #C6FABD + #204C20 + #FFFFFF + #05350C + #FFFFFF + #374734 + #FFFFFF + #223020 + #FFFFFF + #1A494E + #FFFFFF + #003237 + #FFFFFF + #D8DBD2 + #F7FBF1 + #FFFFFF + #F1F5EB + #ECEFE6 + #E6E9E0 + #E0E4DB + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..319dfdc --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,11 @@ + + 16dp + + 30dp + 15dp + 18dp + 20dp + 10dp + 4dp + 6dp + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..4aa907b --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Car Photo taker 2000 + \ No newline at end of file diff --git a/app/src/main/res/values/theme_overlays.xml b/app/src/main/res/values/theme_overlays.xml new file mode 100644 index 0000000..e81f18f --- /dev/null +++ b/app/src/main/res/values/theme_overlays.xml @@ -0,0 +1,98 @@ + + + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..4319c9b --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,64 @@ + + + +