Skip to content

Commit 3043157

Browse files
committed
Encapsulate barcode scanning details to lib module
1 parent ed15e20 commit 3043157

25 files changed

+444
-232
lines changed

app/build.gradle

+1-5
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,7 @@ android {
3535
}
3636

3737
dependencies {
38-
debugImplementation 'com.google.mlkit:barcode-scanning:17.0.2' // With bundled tflite models
39-
releaseImplementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:17.0.2' // Unbundled tflite models
40-
implementation 'androidx.camera:camera-camera2:1.1.0'
41-
implementation 'androidx.camera:camera-lifecycle:1.1.0'
42-
implementation 'androidx.camera:camera-view:1.1.0'
38+
implementation project(path: ':lib')
4339

4440
implementation 'androidx.core:core-ktx:1.7.0'
4541
implementation 'androidx.appcompat:appcompat:1.5.1'

app/src/main/AndroidManifest.xml

-9
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,7 @@
2323

2424
<category android:name="android.intent.category.LAUNCHER" />
2525
</intent-filter>
26-
27-
<meta-data
28-
android:name="android.app.lib_name"
29-
android:value="" />
3026
</activity>
31-
32-
<!-- If you use unbundled version make sure to use this meta-data -->
33-
<meta-data
34-
android:name="com.google.mlkit.vision.DEPENDENCIES"
35-
android:value="barcode" />
3627
</application>
3728

3829
</manifest>
Original file line numberDiff line numberDiff line change
@@ -1,177 +1,47 @@
11
package nl.storegear.android.mlbarcodescanner
22

33
import android.Manifest
4-
import android.content.Context
5-
import android.content.pm.PackageManager
64
import android.os.Bundle
7-
import android.util.Log
8-
import android.util.Size
95
import androidx.appcompat.app.AppCompatActivity
10-
import androidx.camera.core.CameraSelector
11-
import androidx.camera.core.ImageAnalysis
12-
import androidx.camera.core.ImageProxy
13-
import androidx.camera.core.Preview
14-
import androidx.camera.lifecycle.ProcessCameraProvider
15-
import androidx.core.app.ActivityCompat
16-
import androidx.core.content.ContextCompat
17-
import androidx.lifecycle.ViewModelProvider
18-
import com.google.mlkit.common.MlKitException
6+
import com.buildtoapp.mlbarcodescanner.MLBarcodeCallback
7+
import com.buildtoapp.mlbarcodescanner.MLBarcodeScanner
198
import nl.storegear.android.mlbarcodescanner.databinding.ActivityMainBinding
20-
import nl.storegear.android.mlbarcodescanner.mlkit.BarcodeScannerProcessor
21-
import nl.storegear.android.mlbarcodescanner.mlkit.CameraXBarcodeCallback
22-
import nl.storegear.android.mlbarcodescanner.mlkit.CameraXViewModel
23-
import nl.storegear.android.mlbarcodescanner.mlkit.VisionImageProcessor
24-
import nl.storegear.android.mlbarcodescanner.util.MetricUtils.dpToPx
9+
import nl.storegear.android.mlbarcodescanner.util.MetricUtils
10+
import nl.storegear.android.mlbarcodescanner.util.PermissionUtils
2511

26-
class MainActivity : AppCompatActivity(), CameraXBarcodeCallback {
27-
lateinit var binding: ActivityMainBinding
28-
29-
private lateinit var cameraXViewModel: CameraXViewModel
30-
private var cameraProvider: ProcessCameraProvider? = null
31-
private var previewUseCase: Preview? = null
32-
private var analysisUseCase: ImageAnalysis? = null
33-
private var imageProcessor: VisionImageProcessor? = null
34-
private var needUpdateGraphicOverlayImageSourceInfo = false
35-
private var lensFacing = CameraSelector.LENS_FACING_BACK
36-
private lateinit var cameraSelector: CameraSelector
12+
class MainActivity : AppCompatActivity(), MLBarcodeCallback {
13+
private lateinit var binding: ActivityMainBinding
14+
private lateinit var barcodeScanner: MLBarcodeScanner
3715

3816
override fun onCreate(savedInstanceState: Bundle?) {
3917
super.onCreate(savedInstanceState)
4018
binding = ActivityMainBinding.inflate(layoutInflater)
4119
setContentView(binding.root)
4220

43-
if (!allRuntimePermissionsGranted()) {
44-
getRuntimePermissions()
45-
}
46-
cameraXViewModel = ViewModelProvider(this).get(CameraXViewModel::class.java)
47-
48-
cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
49-
cameraXViewModel.processCameraProvider.observe(this) { provider: ProcessCameraProvider? ->
50-
cameraProvider = provider
51-
bindAllCameraUseCases()
21+
if (!PermissionUtils.allRuntimePermissionsGranted(this, REQUIRED_RUNTIME_PERMISSIONS)) {
22+
PermissionUtils.getRuntimePermissions(this, REQUIRED_RUNTIME_PERMISSIONS)
5223
}
24+
initBarcodeScanner()
5325
}
5426

55-
override fun onPause() {
56-
super.onPause()
57-
imageProcessor?.stop()
58-
}
59-
60-
override fun onResume() {
61-
super.onResume()
62-
bindAllCameraUseCases()
63-
}
64-
65-
override fun onDestroy() {
66-
imageProcessor?.stop()
67-
super.onDestroy()
27+
private fun initBarcodeScanner() {
28+
barcodeScanner = MLBarcodeScanner(
29+
callback = this,
30+
focusBoxSize = MetricUtils.dpToPx(264),
31+
graphicOverlay = binding.graphicOverlay,
32+
previewView = binding.previewViewCameraScanning,
33+
lifecycleOwner = this,
34+
context = this,
35+
drawOverlay = true, // show rectangle around detected barcode
36+
drawBanner = true // show detected barcode value on top of it
37+
)
6838
}
6939

7040
override fun onNewBarcodeScanned(displayValue: String, rawValue: String) {
7141
// todo: you can process your barcode here
7242
}
7343

74-
private fun bindAllCameraUseCases() {
75-
// As required by CameraX API, unbinds all use cases before trying to re-bind any of them.
76-
cameraProvider?.unbindAll()
77-
bindPreviewUseCase()
78-
bindAnalysisUseCase()
79-
}
80-
81-
private fun bindPreviewUseCase() {
82-
if (cameraProvider == null) {
83-
return
84-
}
85-
cameraProvider?.unbind(previewUseCase)
86-
val builder = Preview.Builder()
87-
previewUseCase = builder.build()
88-
previewUseCase?.setSurfaceProvider(binding.previewViewCameraScanning.surfaceProvider)
89-
cameraProvider?.bindToLifecycle(this, cameraSelector, previewUseCase)
90-
}
91-
92-
private fun bindAnalysisUseCase() {
93-
if (cameraProvider == null) {
94-
return
95-
}
96-
cameraProvider?.unbind(analysisUseCase)
97-
imageProcessor?.stop()
98-
imageProcessor = BarcodeScannerProcessor(this, focusBoxSize = dpToPx(264))
99-
100-
val builder = ImageAnalysis.Builder()
101-
builder.setTargetResolution(Size(768, 1024))
102-
analysisUseCase = builder.build()
103-
104-
needUpdateGraphicOverlayImageSourceInfo = true
105-
106-
analysisUseCase?.setAnalyzer(
107-
// imageProcessor.processImageProxy will use another thread to run the detection underneath,
108-
// thus we can just runs the analyzer itself on main thread.
109-
ContextCompat.getMainExecutor(this)
110-
) { imageProxy: ImageProxy ->
111-
if (needUpdateGraphicOverlayImageSourceInfo) {
112-
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
113-
if (rotationDegrees == 0 || rotationDegrees == 180) {
114-
binding.graphicOverlay.setImageSourceInfo(imageProxy.width, imageProxy.height, false)
115-
} else {
116-
binding.graphicOverlay.setImageSourceInfo(imageProxy.height, imageProxy.width, false)
117-
}
118-
needUpdateGraphicOverlayImageSourceInfo = false
119-
}
120-
121-
try {
122-
imageProcessor?.processImageProxy(imageProxy, binding.graphicOverlay)
123-
} catch (e: MlKitException) {
124-
Log.e("TAG", "Failed to process image. Error: " + e.localizedMessage)
125-
}
126-
}
127-
cameraProvider?.bindToLifecycle(this, cameraSelector, analysisUseCase)
128-
}
129-
130-
131-
// GET PERMISSIONS //
132-
private fun allRuntimePermissionsGranted(): Boolean {
133-
for (permission in REQUIRED_RUNTIME_PERMISSIONS) {
134-
permission.let {
135-
if (!isPermissionGranted(this, it)) {
136-
return false
137-
}
138-
}
139-
}
140-
return true
141-
}
142-
143-
private fun getRuntimePermissions() {
144-
val permissionsToRequest = ArrayList<String>()
145-
for (permission in REQUIRED_RUNTIME_PERMISSIONS) {
146-
permission.let {
147-
if (!isPermissionGranted(this, it)) {
148-
permissionsToRequest.add(permission)
149-
}
150-
}
151-
}
152-
153-
if (permissionsToRequest.isNotEmpty()) {
154-
ActivityCompat.requestPermissions(this, permissionsToRequest.toTypedArray(), PERMISSION_REQUESTS)
155-
}
156-
}
157-
158-
private fun isPermissionGranted(context: Context, permission: String): Boolean {
159-
if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) {
160-
Log.i(TAG, "Permission granted: $permission")
161-
return true
162-
}
163-
Log.i(TAG, "Permission NOT granted: $permission")
164-
return false
165-
}
166-
16744
companion object {
168-
private const val TAG = "MLBarcodeScanner"
169-
private const val PERMISSION_REQUESTS = 1
170-
171-
private val REQUIRED_RUNTIME_PERMISSIONS = arrayOf(
172-
Manifest.permission.CAMERA,
173-
Manifest.permission.WRITE_EXTERNAL_STORAGE,
174-
Manifest.permission.READ_EXTERNAL_STORAGE
175-
)
45+
private val REQUIRED_RUNTIME_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
17646
}
17747
}

app/src/main/java/nl/storegear/android/mlbarcodescanner/mlkit/VisionImageProcessor.java

-21
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package nl.storegear.android.mlbarcodescanner.util
2+
3+
import android.app.Activity
4+
import android.content.Context
5+
import android.content.pm.PackageManager
6+
import android.util.Log
7+
import androidx.core.app.ActivityCompat
8+
import androidx.core.content.ContextCompat
9+
10+
object PermissionUtils {
11+
private const val TAG = "MLBarcodeScanner"
12+
private const val PERMISSION_REQUESTS = 1
13+
14+
fun allRuntimePermissionsGranted(activity: Activity, requiredPermissions: Array<String>): Boolean {
15+
for (permission in requiredPermissions) {
16+
permission.let {
17+
if (!isPermissionGranted(activity, it)) {
18+
return false
19+
}
20+
}
21+
}
22+
return true
23+
}
24+
25+
fun getRuntimePermissions(activity: Activity, requiredPermissions: Array<String>) {
26+
val permissionsToRequest = ArrayList<String>()
27+
for (permission in requiredPermissions) {
28+
permission.let {
29+
if (!isPermissionGranted(activity, it)) {
30+
permissionsToRequest.add(permission)
31+
}
32+
}
33+
}
34+
35+
if (permissionsToRequest.isNotEmpty()) {
36+
ActivityCompat.requestPermissions(activity, permissionsToRequest.toTypedArray(), PERMISSION_REQUESTS)
37+
}
38+
}
39+
40+
private fun isPermissionGranted(context: Context, permission: String): Boolean {
41+
if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) {
42+
Log.i(TAG, "Permission granted: $permission")
43+
return true
44+
}
45+
Log.i(TAG, "Permission NOT granted: $permission")
46+
return false
47+
}
48+
}

app/src/main/res/layout/activity_main.xml

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@
44
xmlns:app="http://schemas.android.com/apk/res-auto"
55
xmlns:tools="http://schemas.android.com/tools">
66

7-
87
<androidx.camera.view.PreviewView
98
android:id="@+id/previewView_cameraScanning"
109
android:layout_height="0dp"
1110
android:layout_width="match_parent"
1211
app:layout_constraintTop_toTopOf="parent"
1312
app:layout_constraintBottom_toBottomOf="parent" />
1413

15-
<nl.storegear.android.mlbarcodescanner.mlkit.GraphicOverlay
14+
<com.buildtoapp.mlbarcodescanner.GraphicOverlay
1615
android:id="@+id/graphic_overlay"
1716
android:layout_height="0dp"
1817
android:layout_width="0dp"

lib/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

lib/build.gradle

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
plugins {
2+
id 'com.android.library'
3+
id 'org.jetbrains.kotlin.android'
4+
}
5+
6+
android {
7+
namespace 'com.buildtoapp.mlbarcodescanner'
8+
compileSdk 32
9+
10+
defaultConfig {
11+
minSdk 21
12+
targetSdk 32
13+
14+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15+
consumerProguardFiles "consumer-rules.pro"
16+
}
17+
18+
buildTypes {
19+
release {
20+
minifyEnabled false
21+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22+
}
23+
}
24+
compileOptions {
25+
sourceCompatibility JavaVersion.VERSION_1_8
26+
targetCompatibility JavaVersion.VERSION_1_8
27+
}
28+
kotlinOptions {
29+
jvmTarget = '1.8'
30+
}
31+
}
32+
33+
dependencies {
34+
debugImplementation 'com.google.mlkit:barcode-scanning:17.0.2' // With bundled tflite models
35+
releaseImplementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:17.0.0' // Unbundled tflite models
36+
implementation 'androidx.camera:camera-camera2:1.1.0'
37+
implementation 'androidx.camera:camera-lifecycle:1.1.0'
38+
api 'androidx.camera:camera-view:1.1.0'
39+
40+
implementation 'androidx.core:core-ktx:1.7.0'
41+
implementation 'androidx.appcompat:appcompat:1.5.1'
42+
implementation 'com.google.android.material:material:1.7.0'
43+
testImplementation 'junit:junit:4.13.2'
44+
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
45+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
46+
}

lib/consumer-rules.pro

Whitespace-only changes.

lib/proguard-rules.pro

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile

0 commit comments

Comments
 (0)