diff --git a/examples/android/CHIPTool/.idea/jarRepositories.xml b/examples/android/CHIPTool/.idea/jarRepositories.xml
index a5f05cd8c87d4a..e34606ccde7a9d 100644
--- a/examples/android/CHIPTool/.idea/jarRepositories.xml
+++ b/examples/android/CHIPTool/.idea/jarRepositories.xml
@@ -21,5 +21,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/android/CHIPTool/app/build.gradle b/examples/android/CHIPTool/app/build.gradle
index e38c8448e9bd8c..118b2884adfee1 100644
--- a/examples/android/CHIPTool/app/build.gradle
+++ b/examples/android/CHIPTool/app/build.gradle
@@ -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"
@@ -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'
@@ -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()
diff --git a/examples/android/CHIPTool/app/src/main/AndroidManifest.xml b/examples/android/CHIPTool/app/src/main/AndroidManifest.xml
index ede1f0980aee60..7a2febcf4c3f76 100644
--- a/examples/android/CHIPTool/app/src/main/AndroidManifest.xml
+++ b/examples/android/CHIPTool/app/src/main/AndroidManifest.xml
@@ -23,6 +23,7 @@
diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/BarcodeFragment.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/BarcodeFragment.kt
index 9392a2425ff658..f701d2ca105117 100644
--- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/BarcodeFragment.kt
+++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/BarcodeFragment.kt
@@ -24,6 +24,7 @@ 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
@@ -31,34 +32,44 @@ 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()) {
@@ -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,
@@ -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,
@@ -146,46 +186,20 @@ 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)
@@ -193,16 +207,6 @@ class BarcodeFragment : Fragment(), CHIPBarcodeProcessor.BarcodeDetectionListene
}
}
- 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)
@@ -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() {
@@ -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
}
-}
+}
\ No newline at end of file
diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CHIPBarcodeProcessor.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CHIPBarcodeProcessor.kt
deleted file mode 100644
index 5ce5c7c4924dc7..00000000000000
--- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CHIPBarcodeProcessor.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (c) 2020 Project CHIP Authors
- * All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.google.chip.chiptool.setuppayloadscanner
-
-import com.google.android.gms.vision.Detector
-import com.google.android.gms.vision.barcode.Barcode
-
-/** Processes the detected [Barcode]s from a Camera scan. */
-class CHIPBarcodeProcessor(
- private val listener: BarcodeDetectionListener
-) : Detector.Processor {
-
- override fun receiveDetections(detections: Detector.Detections) {
- val detectedItems = detections.detectedItems
- if (detectedItems.size() <= 0) {
- return
- }
- val barcode = detectedItems.valueAt(0)
- listener.handleScannedQrCode(barcode)
- }
-
- override fun release() {
-
- }
-
- /** Interface for receiving the detected QR Code from a Camera scan. */
- interface BarcodeDetectionListener {
-
- /** Provides the [Barcode] detected in camera scan to the registered listener. */
- fun handleScannedQrCode(barcode: Barcode)
- }
-}
diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CameraSourceView.java b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CameraSourceView.java
deleted file mode 100644
index 41b4f6bbfd5d64..00000000000000
--- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/CameraSourceView.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (c) 2020 Project CHIP Authors
- * All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.google.chip.chiptool.setuppayloadscanner;
-
-import android.Manifest;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.ViewGroup;
-import androidx.annotation.RequiresPermission;
-import com.google.android.gms.vision.CameraSource;
-import java.io.IOException;
-
-/** Class that displays the camera view. */
-public class CameraSourceView extends ViewGroup {
-
- private static final String TAG = CameraSourceView.class.getSimpleName();
-
- private SurfaceView surfaceView;
- private boolean startRequested;
- private boolean surfaceAvailable;
- private CameraSource cameraSource;
-
- public CameraSourceView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- surfaceView = new SurfaceView(context);
- surfaceView.getHolder().addCallback(new SurfaceCallback());
- addView(surfaceView);
- }
-
- /**
- * Attempts to start the camera source.
- *
- * @param cameraSource the camera source
- * @throws IOException if the camera doesn't work
- * @throws SecurityException if app doesn't have permission to access camera
- */
- @RequiresPermission(Manifest.permission.CAMERA)
- public void start(CameraSource cameraSource) throws IOException, SecurityException {
- if (cameraSource == null) {
- stop();
- }
-
- this.cameraSource = cameraSource;
-
- if (this.cameraSource != null) {
- startRequested = true;
- startIfReady();
- }
- }
-
- /** Stops the camera source. */
- public void stop() {
- if (cameraSource != null) {
- cameraSource.stop();
- }
- }
-
- /** Releases the camera source. */
- public void release() {
- if (cameraSource != null) {
- cameraSource.release();
- cameraSource = null;
- }
- }
-
- @RequiresPermission(Manifest.permission.CAMERA)
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- final int layoutWidth = right - left;
- final int layoutHeight = bottom - top;
-
- for (int position = 0; position < getChildCount(); ++position) {
- getChildAt(position).layout(0, 0, layoutWidth, layoutHeight);
- }
-
- startIfReady();
- }
-
- @RequiresPermission(Manifest.permission.CAMERA)
- private void startIfReady() {
- try {
- if (startRequested && surfaceAvailable) {
- cameraSource.start(surfaceView.getHolder());
- startRequested = false;
- }
- } catch (SecurityException se) {
- Log.e(TAG, "Do not have permission to start the camera", se);
- } catch (IOException ioe) {
- Log.e(TAG, "Could not start camera source.", ioe);
- }
- }
-
- /** Class to handle surface traits and availability */
- private class SurfaceCallback implements SurfaceHolder.Callback {
-
- @RequiresPermission(Manifest.permission.CAMERA)
- @Override
- public void surfaceCreated(SurfaceHolder surface) {
- surfaceAvailable = true;
- startIfReady();
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder surface) {
- surfaceAvailable = false;
- }
-
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
- }
-}
diff --git a/examples/android/CHIPTool/app/src/main/res/layout/barcode_fragment.xml b/examples/android/CHIPTool/app/src/main/res/layout/barcode_fragment.xml
index 357b402ebeb8bb..b8a23d960c75f3 100644
--- a/examples/android/CHIPTool/app/src/main/res/layout/barcode_fragment.xml
+++ b/examples/android/CHIPTool/app/src/main/res/layout/barcode_fragment.xml
@@ -4,7 +4,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
-