Skip to content
This repository was archived by the owner on Mar 8, 2024. It is now read-only.

Commit 1b33a40

Browse files
Merge pull request #46 from brightec/task/camerax-example
Task/camerax example
2 parents 965d951 + 38c830a commit 1b33a40

File tree

9 files changed

+305
-2
lines changed

9 files changed

+305
-2
lines changed

app/build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ dependencies {
7777
implementation "com.google.android.material:material:1.3.0"
7878
implementation platform("com.google.firebase:firebase-bom:28.1.0")
7979
implementation "com.google.firebase:firebase-core"
80+
// For CameraX example only
81+
implementation "androidx.camera:camera-camera2:1.0.0"
82+
implementation "androidx.camera:camera-lifecycle:1.0.0"
83+
implementation "androidx.camera:camera-view:1.0.0-alpha25"
84+
implementation "com.google.android.gms:play-services-mlkit-barcode-scanning:16.1.5"
8085

8186
//region Local Unit Tests
8287
testImplementation "junit:junit:4.13.2"

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
android:name="android.hardware.camera"
1010
android:required="true" />
1111

12+
<!-- For CameraX example only -->
13+
<uses-permission android:name="android.permission.CAMERA" />
14+
1215
<application
1316
android:name=".Application"
1417
android:allowBackup="true"
@@ -30,6 +33,7 @@
3033
<activity android:name=".XmlJavaActivity" />
3134
<activity android:name=".ProgrammaticActivity" />
3235
<activity android:name=".viewfinder.ViewfinderActivity" />
36+
<activity android:name=".camerax.CameraXActivity" />
3337
<!--suppress AndroidDomInspection - For espresso tests-->
3438
<activity android:name=".testutil.SingleFragmentActivity" />
3539
</application>

app/src/main/java/uk/co/brightec/kbarcode/app/MainActivity.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package uk.co.brightec.kbarcode.app
33
import android.os.Bundle
44
import androidx.appcompat.app.AppCompatActivity
55
import kotlinx.android.synthetic.main.activity_main.*
6+
import uk.co.brightec.kbarcode.app.camerax.CameraXActivity
67
import uk.co.brightec.kbarcode.app.viewfinder.ViewfinderActivity
78

89
class MainActivity : AppCompatActivity() {
@@ -27,5 +28,9 @@ class MainActivity : AppCompatActivity() {
2728
val intent = ViewfinderActivity.getStartingIntent(this)
2829
startActivity(intent)
2930
}
31+
button_camerax.setOnClickListener {
32+
val intent = CameraXActivity.getStartingIntent(this)
33+
startActivity(intent)
34+
}
3035
}
3136
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package uk.co.brightec.kbarcode.app.camerax
2+
3+
import android.content.Context
4+
import android.util.Log
5+
import androidx.annotation.OptIn
6+
import androidx.camera.core.ExperimentalGetImage
7+
import androidx.camera.core.ImageAnalysis
8+
import androidx.camera.core.ImageProxy
9+
import androidx.core.content.ContextCompat
10+
import com.google.android.gms.tasks.Tasks
11+
import com.google.mlkit.vision.barcode.Barcode
12+
import com.google.mlkit.vision.barcode.BarcodeScanner
13+
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
14+
import com.google.mlkit.vision.barcode.BarcodeScanning
15+
import com.google.mlkit.vision.common.InputImage
16+
import java.util.concurrent.ExecutionException
17+
18+
internal class BarcodeAnalyzer(
19+
context: Context,
20+
private val barcodeListener: (List<Barcode>) -> Unit,
21+
) : ImageAnalysis.Analyzer {
22+
23+
private val tag = BarcodeAnalyzer::class.simpleName ?: "BarcodeAnalyzer"
24+
private val mainExecutor = ContextCompat.getMainExecutor(context)
25+
private val scanner: BarcodeScanner = createScanner()
26+
27+
@OptIn(ExperimentalGetImage::class)
28+
override fun analyze(imageProxy: ImageProxy) {
29+
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
30+
val image = imageProxy.image
31+
if (image != null) {
32+
val inputImage = InputImage.fromMediaImage(image, rotationDegrees)
33+
val task = scanner.process(inputImage)
34+
try {
35+
val result = Tasks.await(task)
36+
if (result.isNotEmpty()) {
37+
mainExecutor.execute {
38+
barcodeListener.invoke(result)
39+
}
40+
}
41+
} catch (e: ExecutionException) {
42+
Log.e(tag, "Scanner process failed", e)
43+
} catch (e: InterruptedException) {
44+
Log.e(tag, "Scanner process interrupted", e)
45+
}
46+
}
47+
imageProxy.close()
48+
}
49+
50+
private fun createScanner(): BarcodeScanner {
51+
val options = BarcodeScannerOptions.Builder()
52+
val formats = intArrayOf(
53+
Barcode.FORMAT_CODABAR,
54+
Barcode.FORMAT_EAN_13,
55+
Barcode.FORMAT_EAN_8,
56+
Barcode.FORMAT_ITF,
57+
Barcode.FORMAT_UPC_A,
58+
Barcode.FORMAT_UPC_E
59+
)
60+
if (formats.size > 1) {
61+
@Suppress("SpreadOperator") // Required by Google API
62+
options.setBarcodeFormats(
63+
formats[0], *formats.slice(IntRange(1, formats.size - 1)).toIntArray()
64+
)
65+
} else if (formats.size == 1) {
66+
options.setBarcodeFormats(formats[0])
67+
}
68+
return BarcodeScanning.getClient(options.build())
69+
}
70+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package uk.co.brightec.kbarcode.app.camerax
2+
3+
import android.Manifest
4+
import android.content.Context
5+
import android.content.DialogInterface
6+
import android.content.Intent
7+
import android.content.pm.PackageManager
8+
import android.os.Bundle
9+
import android.util.Log
10+
import android.view.MotionEvent
11+
import androidx.appcompat.app.AlertDialog
12+
import androidx.appcompat.app.AppCompatActivity
13+
import androidx.camera.core.Camera
14+
import androidx.camera.core.CameraControl
15+
import androidx.camera.core.CameraSelector
16+
import androidx.camera.core.FocusMeteringAction
17+
import androidx.camera.core.ImageAnalysis
18+
import androidx.camera.core.Preview
19+
import androidx.camera.lifecycle.ProcessCameraProvider
20+
import androidx.core.app.ActivityCompat
21+
import androidx.core.content.ContextCompat
22+
import kotlinx.android.synthetic.main.activity_camerax.*
23+
import kotlinx.android.synthetic.main.activity_camerax.text_barcodes
24+
import kotlinx.android.synthetic.main.activity_programmatic.*
25+
import uk.co.brightec.kbarcode.app.R
26+
import java.util.concurrent.ExecutionException
27+
import java.util.concurrent.ExecutorService
28+
import java.util.concurrent.Executors
29+
30+
internal class CameraXActivity :
31+
AppCompatActivity(), ActivityCompat.OnRequestPermissionsResultCallback {
32+
33+
private val tag = CameraXActivity::class.simpleName ?: "CameraXActivity"
34+
35+
private lateinit var cameraExecutor: ExecutorService
36+
private lateinit var camera: Camera
37+
38+
override fun onCreate(savedInstanceState: Bundle?) {
39+
super.onCreate(savedInstanceState)
40+
setContentView(R.layout.activity_camerax)
41+
setTitle(R.string.title_camerax)
42+
43+
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
44+
!= PackageManager.PERMISSION_GRANTED
45+
) {
46+
requestCameraPermission()
47+
} else {
48+
startCamera()
49+
}
50+
51+
cameraExecutor = Executors.newSingleThreadExecutor()
52+
}
53+
54+
override fun onDestroy() {
55+
cameraExecutor.shutdown()
56+
super.onDestroy()
57+
}
58+
59+
override fun onRequestPermissionsResult(
60+
requestCode: Int,
61+
permissions: Array<String>,
62+
grantResults: IntArray
63+
) {
64+
when (requestCode) {
65+
REQUEST_PERMISSION_CAMERA -> if (
66+
grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
67+
) {
68+
startCamera()
69+
}
70+
else ->
71+
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
72+
}
73+
}
74+
75+
private fun requestCameraPermission() {
76+
if (ActivityCompat.shouldShowRequestPermissionRationale(
77+
this,
78+
Manifest.permission.CAMERA
79+
)
80+
) {
81+
AlertDialog.Builder(this)
82+
.setTitle(R.string.title_camera_rationale)
83+
.setMessage(R.string.message_camera_rationale)
84+
.setPositiveButton(R.string.action_ok) { _: DialogInterface, _: Int ->
85+
ActivityCompat.requestPermissions(
86+
this,
87+
arrayOf(Manifest.permission.CAMERA),
88+
REQUEST_PERMISSION_CAMERA
89+
)
90+
}
91+
.show()
92+
} else {
93+
ActivityCompat.requestPermissions(
94+
this,
95+
arrayOf(Manifest.permission.CAMERA),
96+
REQUEST_PERMISSION_CAMERA
97+
)
98+
}
99+
}
100+
101+
private fun startCamera() {
102+
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
103+
cameraProviderFuture.addListener(
104+
{
105+
val previewUseCase = Preview.Builder()
106+
.build()
107+
.also {
108+
it.setSurfaceProvider(preview.surfaceProvider)
109+
}
110+
val imageAnalyzerUseCase = ImageAnalysis.Builder()
111+
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
112+
.build()
113+
.also {
114+
it.setAnalyzer(
115+
cameraExecutor,
116+
BarcodeAnalyzer(this) { barcodes ->
117+
val builder = StringBuilder()
118+
for (barcode in barcodes) {
119+
builder.append(barcode.displayValue).append("\n")
120+
}
121+
text_barcodes.text = builder.toString()
122+
}
123+
)
124+
}
125+
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
126+
cameraProvider.unbindAll()
127+
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
128+
camera = cameraProvider.bindToLifecycle(
129+
this, cameraSelector, previewUseCase, imageAnalyzerUseCase
130+
)
131+
setupTapToFocus()
132+
},
133+
ContextCompat.getMainExecutor(this)
134+
)
135+
}
136+
137+
private fun setupTapToFocus() {
138+
preview.setOnTouchListener { view, event ->
139+
val actionMasked = event.actionMasked
140+
if (actionMasked == MotionEvent.ACTION_UP) {
141+
view.performClick()
142+
return@setOnTouchListener false
143+
}
144+
if (actionMasked != MotionEvent.ACTION_DOWN) {
145+
return@setOnTouchListener false
146+
}
147+
148+
val cameraControl = camera.cameraControl
149+
val factory = preview.meteringPointFactory
150+
val point = factory.createPoint(event.x, event.y)
151+
val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
152+
.addPoint(point, FocusMeteringAction.FLAG_AE)
153+
.addPoint(point, FocusMeteringAction.FLAG_AWB)
154+
.build()
155+
val future = cameraControl.startFocusAndMetering(action)
156+
future.addListener(
157+
{
158+
try {
159+
val result = future.get()
160+
Log.d(tag, "Focus Success: ${result.isFocusSuccessful}")
161+
} catch (e: ExecutionException) {
162+
if (e.cause is CameraControl.OperationCanceledException) {
163+
Log.d(tag, "Focus cancelled")
164+
} else {
165+
Log.e(tag, "Focus failed", e)
166+
}
167+
} catch (e: InterruptedException) {
168+
Log.e(tag, "Focus interrupted", e)
169+
}
170+
},
171+
cameraExecutor
172+
)
173+
true
174+
}
175+
}
176+
177+
companion object {
178+
179+
private const val REQUEST_PERMISSION_CAMERA = 1
180+
181+
fun getStartingIntent(context: Context) = Intent(context, CameraXActivity::class.java)
182+
}
183+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:layout_width="match_parent"
6+
android:layout_height="match_parent"
7+
android:keepScreenOn="true"
8+
tools:background="@color/black">
9+
10+
<androidx.camera.view.PreviewView
11+
android:id="@+id/preview"
12+
android:layout_width="match_parent"
13+
android:layout_height="match_parent" />
14+
15+
<TextView
16+
android:id="@+id/text_barcodes"
17+
android:layout_width="120dp"
18+
android:layout_height="wrap_content"
19+
android:background="@color/black"
20+
android:minLines="2"
21+
android:textColor="@color/white"
22+
app:layout_constraintBottom_toBottomOf="parent"
23+
app:layout_constraintStart_toStartOf="parent" />
24+
</androidx.constraintlayout.widget.ConstraintLayout>

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,18 @@
4141
android:layout_width="wrap_content"
4242
android:layout_height="wrap_content"
4343
android:text="@string/title_viewfinder"
44-
app:layout_constraintBottom_toBottomOf="parent"
44+
app:layout_constraintBottom_toTopOf="@+id/button_camerax"
4545
app:layout_constraintEnd_toEndOf="parent"
4646
app:layout_constraintStart_toStartOf="parent"
4747
app:layout_constraintTop_toBottomOf="@+id/button_programmatic" />
48+
49+
<Button
50+
android:id="@+id/button_camerax"
51+
android:layout_width="wrap_content"
52+
android:layout_height="wrap_content"
53+
android:text="@string/title_camerax"
54+
app:layout_constraintBottom_toBottomOf="parent"
55+
app:layout_constraintEnd_toEndOf="parent"
56+
app:layout_constraintStart_toStartOf="parent"
57+
app:layout_constraintTop_toBottomOf="@+id/button_viewfinder" />
4858
</androidx.constraintlayout.widget.ConstraintLayout>

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<string name="title_programmatic">Programmatic Based</string>
77
<string name="title_viewfinder">Viewfinder Based</string>
88
<string name="title_camera_rationale">Why camera?</string>
9+
<string name="title_camerax">CameraX Example</string>
910

1011
<string name="message_camera_rationale">We use your camera to scan barcodes</string>
1112

kbarcode/src/main/java/uk/co/brightec/kbarcode/BarcodeView.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ public class BarcodeView @JvmOverloads constructor(
197197

198198
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
199199
val childRect = calculateRectForChild(
200-
previewScaleType = _previewScaleType, left = left, top = top, right = right, bottom = bottom
200+
previewScaleType = _previewScaleType,
201+
left = left, top = top, right = right, bottom = bottom
201202
)
202203

203204
for (i in 0 until childCount) {

0 commit comments

Comments
 (0)