@@ -24,41 +24,52 @@ import android.content.pm.PackageManager
24
24
import android.os.Bundle
25
25
import android.os.Handler
26
26
import android.os.Looper
27
+ import android.util.DisplayMetrics
27
28
import android.util.Log
28
29
import android.view.LayoutInflater
29
30
import android.view.View
30
31
import android.view.ViewGroup
31
32
import android.widget.Button
32
33
import android.widget.EditText
33
34
import android.widget.Toast
34
- import androidx.annotation.RequiresPermission
35
35
import androidx.appcompat.app.AlertDialog
36
+ import androidx.camera.core.*
37
+ import androidx.camera.lifecycle.ProcessCameraProvider
38
+ import androidx.camera.view.PreviewView
39
+ import androidx.core.content.ContextCompat
36
40
import androidx.core.content.ContextCompat.checkSelfPermission
37
41
import androidx.fragment.app.Fragment
38
42
import chip.setuppayload.SetupPayload
39
43
import chip.setuppayload.SetupPayloadParser
40
- import chip.setuppayload.SetupPayloadParser.SetupPayloadException
41
44
import chip.setuppayload.SetupPayloadParser.UnrecognizedQrCodeException
42
- import com.google.android.gms.vision.CameraSource
43
- import com.google.android.gms.vision.barcode.Barcode
44
- import com.google.android.gms.vision.barcode.BarcodeDetector
45
45
import com.google.chip.chiptool.R
46
46
import com.google.chip.chiptool.SelectActionFragment
47
47
import com.google.chip.chiptool.util.FragmentUtil
48
- import java.io.IOException
48
+ import com.google.mlkit.vision.barcode.BarcodeScanner
49
+ import com.google.mlkit.vision.barcode.BarcodeScanning
50
+ import com.google.mlkit.vision.barcode.common.Barcode
51
+ import com.google.mlkit.vision.common.InputImage
49
52
import kotlinx.android.synthetic.main.barcode_fragment.view.inputAddressBtn
53
+ import java.util.concurrent.Executors
54
+ import kotlin.math.abs
55
+ import kotlin.math.max
56
+ import kotlin.math.min
50
57
51
58
/* * Launches the camera to scan for QR code. */
52
- class BarcodeFragment : Fragment (), CHIPBarcodeProcessor.BarcodeDetectionListener {
53
-
54
- private var cameraSource: CameraSource ? = null
55
- private var cameraSourceView: CameraSourceView ? = null
56
- private var barcodeDetector: BarcodeDetector ? = null
57
- private var cameraStarted = false
59
+ class BarcodeFragment : Fragment () {
58
60
61
+ private lateinit var previewView: PreviewView
59
62
private var manualCodeEditText: EditText ? = null
60
63
private var manualCodeBtn: Button ? = null
61
64
65
+ private fun aspectRatio (width : Int , height : Int ): Int {
66
+ val previewRatio = max(width, height).toDouble() / min(width, height)
67
+ if (abs(previewRatio - RATIO_4_3_VALUE ) <= abs(previewRatio - RATIO_16_9_VALUE )) {
68
+ return AspectRatio .RATIO_4_3
69
+ }
70
+ return AspectRatio .RATIO_16_9
71
+ }
72
+
62
73
override fun onCreate (savedInstanceState : Bundle ? ) {
63
74
super .onCreate(savedInstanceState)
64
75
if (! hasCameraPermission()) {
@@ -72,11 +83,10 @@ class BarcodeFragment : Fragment(), CHIPBarcodeProcessor.BarcodeDetectionListene
72
83
savedInstanceState : Bundle ?
73
84
): View {
74
85
return inflater.inflate(R .layout.barcode_fragment, container, false ).apply {
75
- cameraSourceView = findViewById(R .id.camera_view)
76
-
86
+ previewView = findViewById(R .id.camera_view)
77
87
manualCodeEditText = findViewById(R .id.manualCodeEditText)
78
88
manualCodeBtn = findViewById(R .id.manualCodeBtn)
79
-
89
+ startCamera()
80
90
inputAddressBtn.setOnClickListener {
81
91
FragmentUtil .getHost(
82
92
this @BarcodeFragment,
@@ -86,46 +96,76 @@ class BarcodeFragment : Fragment(), CHIPBarcodeProcessor.BarcodeDetectionListene
86
96
}
87
97
}
88
98
89
- override fun onViewCreated (view : View , savedInstanceState : Bundle ? ) {
90
- super .onViewCreated(view, savedInstanceState)
91
- initializeBarcodeDetectorAndCamera()
92
- }
99
+ @SuppressLint(" UnsafeOptInUsageError" )
100
+ private fun startCamera () {
101
+ val cameraProviderFuture = ProcessCameraProvider .getInstance(requireActivity())
102
+ cameraProviderFuture.addListener({
103
+ val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
104
+ val metrics = DisplayMetrics ().also { previewView.display?.getRealMetrics(it) }
105
+ val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
106
+ // Preview
107
+ val preview: Preview = Preview .Builder ()
108
+ .setTargetAspectRatio(screenAspectRatio)
109
+ .setTargetRotation(previewView.display.rotation)
110
+ .build()
111
+ preview.setSurfaceProvider(previewView.surfaceProvider)
93
112
94
- @SuppressLint(" MissingPermission" )
95
- override fun onResume () {
96
- super .onResume()
113
+ // Setup barcode scanner
114
+ val imageAnalysis = ImageAnalysis .Builder ()
115
+ .setTargetAspectRatio(screenAspectRatio)
116
+ .setTargetRotation(previewView.display.rotation)
117
+ .build()
118
+ val cameraExecutor = Executors .newSingleThreadExecutor()
119
+ val barcodeScanner: BarcodeScanner = BarcodeScanning .getClient()
120
+ imageAnalysis.setAnalyzer(cameraExecutor) { imageProxy ->
121
+ processImageProxy(barcodeScanner, imageProxy)
122
+ }
123
+ // Select back camera as a default
124
+ val cameraSelector = CameraSelector .DEFAULT_BACK_CAMERA
125
+ try {
126
+ // Unbind use cases before rebinding
127
+ cameraProvider.unbindAll()
97
128
98
- if (hasCameraPermission() && ! cameraStarted) {
99
- startCamera()
100
- }
101
- }
129
+ // Bind use cases to camera
130
+ cameraProvider.bindToLifecycle(this , cameraSelector, preview, imageAnalysis)
102
131
103
- private fun initializeBarcodeDetectorAndCamera () {
104
- barcodeDetector?.let { detector ->
105
- if (! detector.isOperational) {
106
- showCameraUnavailableAlert()
132
+ } catch (exc: Exception ) {
133
+ Log .e(TAG , " Use case binding failed" , exc)
107
134
}
108
- return
109
- }
110
-
111
- val context = requireContext()
112
- barcodeDetector = BarcodeDetector .Builder (context).build().apply {
113
- setProcessor(CHIPBarcodeProcessor (this @BarcodeFragment))
114
- }
115
- cameraSource = CameraSource .Builder (context, barcodeDetector)
116
- .setFacing(CameraSource .CAMERA_FACING_BACK )
117
- .setAutoFocusEnabled(true )
118
- .setRequestedFps(30.0f )
119
- .build()
135
+ }, ContextCompat .getMainExecutor(requireActivity()))
120
136
121
137
// workaround: can not use gms to scan the code in China, added a EditText to debug
122
138
manualCodeBtn?.setOnClickListener {
123
- var qrCode = manualCodeEditText?.text.toString()
139
+ val qrCode = manualCodeEditText?.text.toString()
124
140
Log .d(TAG , " Submit Code:$qrCode " )
125
141
handleInputQrCode(qrCode)
126
142
}
127
143
}
128
144
145
+ @ExperimentalGetImage
146
+ private fun processImageProxy (
147
+ barcodeScanner : BarcodeScanner ,
148
+ imageProxy : ImageProxy
149
+ ) {
150
+ val inputImage =
151
+ InputImage .fromMediaImage(imageProxy.image!! , imageProxy.imageInfo.rotationDegrees)
152
+
153
+ barcodeScanner.process(inputImage)
154
+ .addOnSuccessListener { barcodes ->
155
+ barcodes.forEach {
156
+ handleScannedQrCode(it)
157
+ }
158
+ }
159
+ .addOnFailureListener {
160
+ Log .e(TAG , it.message ? : it.toString())
161
+ }.addOnCompleteListener {
162
+ // When the image is from CameraX analysis use case, must call image.close() on received
163
+ // images when finished using them. Otherwise, new images may not be received or the camera
164
+ // may stall.
165
+ imageProxy.close()
166
+ }
167
+ }
168
+
129
169
override fun onRequestPermissionsResult (
130
170
requestCode : Int ,
131
171
permissions : Array <String >,
@@ -146,63 +186,27 @@ class BarcodeFragment : Fragment(), CHIPBarcodeProcessor.BarcodeDetectionListene
146
186
payload = SetupPayloadParser ().parseQrCode(qrCode)
147
187
} catch (ex: UnrecognizedQrCodeException ) {
148
188
Log .e(TAG , " Unrecognized QR Code" , ex)
149
- Toast .makeText(requireContext(), " Unrecognized QR Code : ${ex.message} " , Toast .LENGTH_SHORT ).show()
150
- payload = SetupPayload ()
151
- return
152
- } catch (ex: SetupPayloadException ) {
153
- Log .e(TAG , " Exception " , ex)
154
- Toast .makeText(requireContext(), " Exception : ${ex.message} " , Toast .LENGTH_SHORT ).show()
155
- payload = SetupPayload ()
156
- return
189
+ Toast .makeText(requireContext(), " Unrecognized QR Code" , Toast .LENGTH_SHORT ).show()
157
190
}
158
191
FragmentUtil .getHost(this , Callback ::class .java)
159
192
?.onCHIPDeviceInfoReceived(CHIPDeviceInfo .fromSetupPayload(payload))
160
193
}
161
194
162
- @SuppressLint(" MissingPermission" )
163
- override fun handleScannedQrCode (barcode : Barcode ) {
195
+ private fun handleScannedQrCode (barcode : Barcode ) {
164
196
Handler (Looper .getMainLooper()).post {
165
- stopCamera()
166
-
167
197
lateinit var payload: SetupPayload
168
198
try {
169
199
payload = SetupPayloadParser ().parseQrCode(barcode.displayValue)
170
200
} catch (ex: UnrecognizedQrCodeException ) {
171
201
Log .e(TAG , " Unrecognized QR Code" , ex)
172
202
Toast .makeText(requireContext(), " Unrecognized QR Code" , Toast .LENGTH_SHORT ).show()
173
-
174
- // Restart camera view.
175
- if (hasCameraPermission() && ! cameraStarted) {
176
- startCamera()
177
- }
178
- payload = SetupPayload ()
179
- return @post
180
- } catch (ex: SetupPayloadException ) {
181
- Log .e(TAG , " Exception " , ex)
182
- Toast .makeText(requireContext(), " Exception : ${ex.message} " , Toast .LENGTH_SHORT ).show()
183
-
184
- // Restart camera view.
185
- if (hasCameraPermission() && ! cameraStarted) {
186
- startCamera()
187
- }
188
- payload = SetupPayload ()
189
203
return @post
190
204
}
191
205
FragmentUtil .getHost(this , Callback ::class .java)
192
206
?.onCHIPDeviceInfoReceived(CHIPDeviceInfo .fromSetupPayload(payload))
193
207
}
194
208
}
195
209
196
- override fun onPause () {
197
- super .onPause()
198
- stopCamera()
199
- }
200
-
201
- override fun onDestroy () {
202
- super .onDestroy()
203
- cameraSourceView?.release()
204
- }
205
-
206
210
private fun showCameraPermissionAlert () {
207
211
AlertDialog .Builder (requireContext())
208
212
.setTitle(R .string.camera_permission_missing_alert_title)
@@ -227,24 +231,9 @@ class BarcodeFragment : Fragment(), CHIPBarcodeProcessor.BarcodeDetectionListene
227
231
.show()
228
232
}
229
233
230
- @RequiresPermission(Manifest .permission.CAMERA )
231
- private fun startCamera () {
232
- try {
233
- cameraSourceView?.start(cameraSource)
234
- cameraStarted = true
235
- } catch (e: IOException ) {
236
- Log .e(TAG , " Unable to start camera source." , e)
237
- }
238
- }
239
-
240
- private fun stopCamera () {
241
- cameraSourceView?.stop()
242
- cameraStarted = false
243
- }
244
-
245
234
private fun hasCameraPermission (): Boolean {
246
235
return (PackageManager .PERMISSION_GRANTED
247
- == checkSelfPermission(requireContext(), Manifest .permission.CAMERA ))
236
+ == checkSelfPermission(requireContext(), Manifest .permission.CAMERA ))
248
237
}
249
238
250
239
private fun requestCameraPermission () {
@@ -262,6 +251,10 @@ class BarcodeFragment : Fragment(), CHIPBarcodeProcessor.BarcodeDetectionListene
262
251
private const val TAG = " BarcodeFragment"
263
252
private const val REQUEST_CODE_CAMERA_PERMISSION = 100 ;
264
253
265
- @JvmStatic fun newInstance () = BarcodeFragment ()
254
+ @JvmStatic
255
+ fun newInstance () = BarcodeFragment ()
256
+
257
+ private const val RATIO_4_3_VALUE = 4.0 / 3.0
258
+ private const val RATIO_16_9_VALUE = 16.0 / 9.0
266
259
}
267
- }
260
+ }
0 commit comments