Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Replaced play-services-vision with mlkit:barcode-scanning #23090

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/android/CHIPTool/.idea/jarRepositories.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 9 additions & 3 deletions examples/android/CHIPTool/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 30
compileSdkVersion 31

defaultConfig {
applicationId "com.google.chip.chiptool"
minSdkVersion 24
targetSdkVersion 30
targetSdkVersion 31
versionCode 1
versionName "1.0"

Expand Down Expand Up @@ -82,7 +82,6 @@ dependencies {

implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.preference:preference:1.1.1'
implementation "com.google.android.gms:play-services-vision:20.1.0"
implementation 'androidx.fragment:fragment:1.3.0-beta01'
implementation "androidx.annotation:annotation:1.1.0"
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
Expand All @@ -96,6 +95,13 @@ dependencies {
implementation "androidx.work:work-runtime:2.3.3"
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.jjoe64:graphview:4.2.2'

implementation 'com.google.mlkit:barcode-scanning:17.0.2'
def camerax_version = "1.1.0"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-view:${camerax_version}"
}
repositories {
mavenCentral()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<activity
android:name=".CHIPToolActivity"
android:label="@string/app_name"
android:exported="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,41 +24,52 @@ import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.annotation.RequiresPermission
import androidx.appcompat.app.AlertDialog
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.checkSelfPermission
import androidx.fragment.app.Fragment
import chip.setuppayload.SetupPayload
import chip.setuppayload.SetupPayloadParser
import chip.setuppayload.SetupPayloadParser.SetupPayloadException
import chip.setuppayload.SetupPayloadParser.UnrecognizedQrCodeException
import com.google.android.gms.vision.CameraSource
import com.google.android.gms.vision.barcode.Barcode
import com.google.android.gms.vision.barcode.BarcodeDetector
import com.google.chip.chiptool.R
import com.google.chip.chiptool.SelectActionFragment
import com.google.chip.chiptool.util.FragmentUtil
import java.io.IOException
import com.google.mlkit.vision.barcode.BarcodeScanner
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import kotlinx.android.synthetic.main.barcode_fragment.view.inputAddressBtn
import java.util.concurrent.Executors
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min

/** Launches the camera to scan for QR code. */
class BarcodeFragment : Fragment(), CHIPBarcodeProcessor.BarcodeDetectionListener {

private var cameraSource: CameraSource? = null
private var cameraSourceView: CameraSourceView? = null
private var barcodeDetector: BarcodeDetector? = null
private var cameraStarted = false
class BarcodeFragment : Fragment() {

private lateinit var previewView: PreviewView
private var manualCodeEditText: EditText? = null
private var manualCodeBtn: Button? = null

private fun aspectRatio(width: Int, height: Int): Int {
val previewRatio = max(width, height).toDouble() / min(width, height)
if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
return AspectRatio.RATIO_4_3
}
return AspectRatio.RATIO_16_9
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!hasCameraPermission()) {
Expand All @@ -72,11 +83,10 @@ class BarcodeFragment : Fragment(), CHIPBarcodeProcessor.BarcodeDetectionListene
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.barcode_fragment, container, false).apply {
cameraSourceView = findViewById(R.id.camera_view)

previewView = findViewById(R.id.camera_view)
manualCodeEditText = findViewById(R.id.manualCodeEditText)
manualCodeBtn = findViewById(R.id.manualCodeBtn)

startCamera()
inputAddressBtn.setOnClickListener {
FragmentUtil.getHost(
this@BarcodeFragment,
Expand All @@ -86,46 +96,76 @@ class BarcodeFragment : Fragment(), CHIPBarcodeProcessor.BarcodeDetectionListene
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initializeBarcodeDetectorAndCamera()
}
@SuppressLint("UnsafeOptInUsageError")
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireActivity())
cameraProviderFuture.addListener({
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
val metrics = DisplayMetrics().also { previewView.display?.getRealMetrics(it) }
val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
// Preview
val preview: Preview = Preview.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(previewView.display.rotation)
.build()
preview.setSurfaceProvider(previewView.surfaceProvider)

@SuppressLint("MissingPermission")
override fun onResume() {
super.onResume()
// Setup barcode scanner
val imageAnalysis = ImageAnalysis.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(previewView.display.rotation)
.build()
val cameraExecutor = Executors.newSingleThreadExecutor()
val barcodeScanner: BarcodeScanner = BarcodeScanning.getClient()
imageAnalysis.setAnalyzer(cameraExecutor) { imageProxy ->
processImageProxy(barcodeScanner, imageProxy)
}
// Select back camera as a default
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()

if (hasCameraPermission() && !cameraStarted) {
startCamera()
}
}
// Bind use cases to camera
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis)

private fun initializeBarcodeDetectorAndCamera() {
barcodeDetector?.let { detector ->
if (!detector.isOperational) {
showCameraUnavailableAlert()
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
return
}

val context = requireContext()
barcodeDetector = BarcodeDetector.Builder(context).build().apply {
setProcessor(CHIPBarcodeProcessor(this@BarcodeFragment))
}
cameraSource = CameraSource.Builder(context, barcodeDetector)
.setFacing(CameraSource.CAMERA_FACING_BACK)
.setAutoFocusEnabled(true)
.setRequestedFps(30.0f)
.build()
}, ContextCompat.getMainExecutor(requireActivity()))

//workaround: can not use gms to scan the code in China, added a EditText to debug
manualCodeBtn?.setOnClickListener {
var qrCode = manualCodeEditText?.text.toString()
val qrCode = manualCodeEditText?.text.toString()
Log.d(TAG, "Submit Code:$qrCode")
handleInputQrCode(qrCode)
}
}

@ExperimentalGetImage
private fun processImageProxy(
barcodeScanner: BarcodeScanner,
imageProxy: ImageProxy
) {
val inputImage =
InputImage.fromMediaImage(imageProxy.image!!, imageProxy.imageInfo.rotationDegrees)

barcodeScanner.process(inputImage)
.addOnSuccessListener { barcodes ->
barcodes.forEach {
handleScannedQrCode(it)
}
}
.addOnFailureListener {
Log.e(TAG, it.message ?: it.toString())
}.addOnCompleteListener {
// When the image is from CameraX analysis use case, must call image.close() on received
// images when finished using them. Otherwise, new images may not be received or the camera
// may stall.
imageProxy.close()
}
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
Expand All @@ -146,63 +186,27 @@ class BarcodeFragment : Fragment(), CHIPBarcodeProcessor.BarcodeDetectionListene
payload = SetupPayloadParser().parseQrCode(qrCode)
} catch (ex: UnrecognizedQrCodeException) {
Log.e(TAG, "Unrecognized QR Code", ex)
Toast.makeText(requireContext(), "Unrecognized QR Code : ${ex.message}", Toast.LENGTH_SHORT).show()
payload = SetupPayload()
return
} catch (ex: SetupPayloadException) {
Log.e(TAG, "Exception ", ex)
Toast.makeText(requireContext(), "Exception : ${ex.message}", Toast.LENGTH_SHORT).show()
payload = SetupPayload()
return
Toast.makeText(requireContext(), "Unrecognized QR Code", Toast.LENGTH_SHORT).show()
}
FragmentUtil.getHost(this, Callback::class.java)
?.onCHIPDeviceInfoReceived(CHIPDeviceInfo.fromSetupPayload(payload))
}

@SuppressLint("MissingPermission")
override fun handleScannedQrCode(barcode: Barcode) {
private fun handleScannedQrCode(barcode: Barcode) {
Handler(Looper.getMainLooper()).post {
stopCamera()

lateinit var payload: SetupPayload
try {
payload = SetupPayloadParser().parseQrCode(barcode.displayValue)
} catch (ex: UnrecognizedQrCodeException) {
Log.e(TAG, "Unrecognized QR Code", ex)
Toast.makeText(requireContext(), "Unrecognized QR Code", Toast.LENGTH_SHORT).show()

// Restart camera view.
if (hasCameraPermission() && !cameraStarted) {
startCamera()
}
payload = SetupPayload()
return@post
} catch (ex: SetupPayloadException) {
Log.e(TAG, "Exception ", ex)
Toast.makeText(requireContext(), "Exception : ${ex.message}", Toast.LENGTH_SHORT).show()

// Restart camera view.
if (hasCameraPermission() && !cameraStarted) {
startCamera()
}
payload = SetupPayload()
return@post
}
FragmentUtil.getHost(this, Callback::class.java)
?.onCHIPDeviceInfoReceived(CHIPDeviceInfo.fromSetupPayload(payload))
}
}

override fun onPause() {
super.onPause()
stopCamera()
}

override fun onDestroy() {
super.onDestroy()
cameraSourceView?.release()
}

private fun showCameraPermissionAlert() {
AlertDialog.Builder(requireContext())
.setTitle(R.string.camera_permission_missing_alert_title)
Expand All @@ -227,24 +231,9 @@ class BarcodeFragment : Fragment(), CHIPBarcodeProcessor.BarcodeDetectionListene
.show()
}

@RequiresPermission(Manifest.permission.CAMERA)
private fun startCamera() {
try {
cameraSourceView?.start(cameraSource)
cameraStarted = true
} catch (e: IOException) {
Log.e(TAG, "Unable to start camera source.", e)
}
}

private fun stopCamera() {
cameraSourceView?.stop()
cameraStarted = false
}

private fun hasCameraPermission(): Boolean {
return (PackageManager.PERMISSION_GRANTED
== checkSelfPermission(requireContext(), Manifest.permission.CAMERA))
== checkSelfPermission(requireContext(), Manifest.permission.CAMERA))
}

private fun requestCameraPermission() {
Expand All @@ -262,6 +251,10 @@ class BarcodeFragment : Fragment(), CHIPBarcodeProcessor.BarcodeDetectionListene
private const val TAG = "BarcodeFragment"
private const val REQUEST_CODE_CAMERA_PERMISSION = 100;

@JvmStatic fun newInstance() = BarcodeFragment()
@JvmStatic
fun newInstance() = BarcodeFragment()

private const val RATIO_4_3_VALUE = 4.0 / 3.0
private const val RATIO_16_9_VALUE = 16.0 / 9.0
}
}
}

This file was deleted.

Loading