Skip to content

Commit 25540f3

Browse files
committed
Modernize Android qrcode module with CameraX and zxing-cpp
The qrcode module used deprecated APIs like android.hardware.Camera and Play Services Vision API. Replace these with AndroidX CameraX and open source zxing-cpp. zxing-cpp works on all devices, no matter if they have Play Services or not.
1 parent 596352d commit 25540f3

File tree

9 files changed

+165
-789
lines changed

9 files changed

+165
-789
lines changed

hellocardboard-android/build.gradle

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
apply plugin: 'com.android.application'
22

33
android {
4-
compileSdkVersion 33
4+
compileSdk = 34
55
lintOptions {
66
abortOnError false
77
}
@@ -16,7 +16,7 @@ android {
1616
//
1717
// See the release notes for details.
1818
minSdkVersion 26
19-
targetSdkVersion 33
19+
targetSdkVersion 34
2020
versionCode 1
2121
versionName "1.25.0"
2222
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -41,12 +41,14 @@ android {
4141

4242
dependencies {
4343
implementation fileTree(dir: 'libs', include: ['*.jar'])
44-
implementation 'androidx.appcompat:appcompat:1.6.1'
45-
// Android Mobile Vision
46-
// TODO(b/213613345) Migrate to ML Kit.
47-
implementation 'com.google.android.gms:play-services-vision:20.1.3'
44+
implementation 'androidx.appcompat:appcompat:1.7.0'
45+
implementation 'io.github.zxing-cpp:android:2.2.0'
4846
implementation 'com.google.android.material:material:1.6.1'
4947
implementation 'com.google.protobuf:protobuf-javalite:3.19.4'
48+
implementation 'androidx.camera:camera-core:1.3.4'
49+
implementation 'androidx.camera:camera-view:1.3.4'
50+
implementation 'androidx.camera:camera-lifecycle:1.3.4'
51+
implementation 'androidx.camera:camera-camera2:1.3.4'
5052
implementation project(":sdk")
5153
}
5254

sdk/build.gradle

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apply plugin: 'com.android.library'
22
apply plugin: 'com.google.protobuf'
33

44
android {
5-
compileSdkVersion 33
5+
compileSdk = 34
66
lintOptions {
77
abortOnError false
88
}
@@ -16,7 +16,7 @@ android {
1616
//
1717
// See the release notes for details.
1818
minSdkVersion 26
19-
targetSdkVersion 33
19+
targetSdkVersion 34
2020
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
2121
ndk {
2222
abiFilters 'armeabi-v7a', 'arm64-v8a'
@@ -69,10 +69,12 @@ protobuf {
6969

7070
dependencies {
7171
implementation fileTree(dir: 'libs', include: ['*.jar'])
72-
implementation 'androidx.appcompat:appcompat:1.6.1'
73-
// Android Mobile Vision
74-
// TODO(b/217176538) Migrate to ML Kit.
75-
implementation 'com.google.android.gms:play-services-vision:20.1.3'
72+
implementation 'androidx.appcompat:appcompat:1.7.0'
73+
implementation 'io.github.zxing-cpp:android:2.2.0'
7674
implementation 'com.google.android.material:material:1.6.1'
7775
implementation 'com.google.protobuf:protobuf-javalite:3.19.4'
76+
implementation 'androidx.camera:camera-core:1.3.4'
77+
implementation 'androidx.camera:camera-view:1.3.4'
78+
implementation 'androidx.camera:camera-lifecycle:1.3.4'
79+
implementation 'androidx.camera:camera-camera2:1.3.4'
7880
}

sdk/qrcode/android/java/com/google/cardboard/sdk/QrCodeCaptureActivity.java

Lines changed: 22 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package com.google.cardboard.sdk;
1717

1818
import android.Manifest;
19-
import android.app.Dialog;
2019
import android.content.Context;
2120
import android.content.Intent;
2221
import android.content.pm.PackageManager;
@@ -30,19 +29,16 @@
3029
import android.widget.Toast;
3130
import androidx.annotation.NonNull;
3231
import androidx.appcompat.app.AppCompatActivity;
32+
import androidx.camera.view.PreviewView;
3333
import androidx.core.app.ActivityCompat;
34-
import com.google.android.gms.common.ConnectionResult;
35-
import com.google.android.gms.common.GoogleApiAvailability;
36-
import com.google.android.gms.vision.MultiProcessor;
37-
import com.google.android.gms.vision.barcode.Barcode;
38-
import com.google.android.gms.vision.barcode.BarcodeDetector;
34+
import zxingcpp.BarcodeReader;
3935
import com.google.cardboard.sdk.qrcode.CardboardParamsUtils;
4036
import com.google.cardboard.sdk.qrcode.QrCodeContentProcessor;
4137
import com.google.cardboard.sdk.qrcode.QrCodeTracker;
42-
import com.google.cardboard.sdk.qrcode.QrCodeTrackerFactory;
4338
import com.google.cardboard.sdk.qrcode.camera.CameraSource;
44-
import com.google.cardboard.sdk.qrcode.camera.CameraSourcePreview;
39+
4540
import java.io.IOException;
41+
import java.util.HashSet;
4642

4743
/**
4844
* Manages the QR code capture activity. It scans permanently with the camera until it finds a valid
@@ -52,17 +48,11 @@ public class QrCodeCaptureActivity extends AppCompatActivity
5248
implements QrCodeTracker.Listener, QrCodeContentProcessor.Listener {
5349
private static final String TAG = QrCodeCaptureActivity.class.getSimpleName();
5450

55-
// Intent request code to handle updating play services if needed.
56-
private static final int RC_HANDLE_GMS = 9001;
57-
5851
// Permission request codes
5952
private static final int PERMISSIONS_REQUEST_CODE = 2;
6053

61-
// Min sdk version required for google play services.
62-
private static final int MIN_SDK_VERSION = 23;
63-
6454
private CameraSource cameraSource;
65-
private CameraSourcePreview cameraSourcePreview;
55+
private PreviewView cameraSourcePreview;
6656

6757
// Flag used to avoid saving the device parameters more than once.
6858
private static boolean qrCodeSaved = false;
@@ -154,26 +144,14 @@ private void launchPermissionsSettings() {
154144

155145
/** Creates and starts the camera. */
156146
private void createCameraSource() {
157-
Context context = getApplicationContext();
158-
159-
BarcodeDetector qrCodeDetector =
160-
new BarcodeDetector.Builder(context).setBarcodeFormats(Barcode.QR_CODE).build();
161-
162-
QrCodeTrackerFactory qrCodeFactory = new QrCodeTrackerFactory(this);
163-
164-
qrCodeDetector.setProcessor(new MultiProcessor.Builder<>(qrCodeFactory).build());
165-
166-
// Check that native dependencies are downloaded.
167-
if (!qrCodeDetector.isOperational()) {
168-
Toast.makeText(this, R.string.missing_dependencies, Toast.LENGTH_LONG).show();
169-
Log.w(
170-
TAG,
171-
"QR Code detector is not operational. Try connecting to WiFi and updating Google Play"
172-
+ " Services or checking that the device storage isn't low.");
173-
}
174-
175-
// Creates and starts the camera.
176-
cameraSource = new CameraSource(getApplicationContext(), qrCodeDetector);
147+
BarcodeReader.Options options = new BarcodeReader.Options();
148+
HashSet<BarcodeReader.Format> formats = new HashSet<>();
149+
formats.add(BarcodeReader.Format.QR_CODE);
150+
options.setFormats(formats);
151+
options.setTextMode(BarcodeReader.TextMode.PLAIN);
152+
BarcodeReader qrCodeDetector = new BarcodeReader(options);
153+
QrCodeTracker tracker = new QrCodeTracker(this);
154+
cameraSource = new CameraSource(this, this, qrCodeDetector, tracker);
177155
}
178156

179157
/** Restarts the camera. */
@@ -197,27 +175,17 @@ protected void onResume() {
197175
@Override
198176
protected void onPause() {
199177
super.onPause();
200-
if (cameraSourcePreview != null) {
201-
cameraSourcePreview.stop();
202-
cameraSourcePreview.release();
178+
if (cameraSource != null) {
179+
cameraSource.release();
180+
cameraSource = null;
203181
}
204182
}
205183

206184
/** Starts or restarts the camera source, if it exists. */
207185
private void startCameraSource() {
208-
// Check that the device has play services available.
209-
int code =
210-
GoogleApiAvailability.getInstance()
211-
.isGooglePlayServicesAvailable(getApplicationContext(), MIN_SDK_VERSION);
212-
if (code != ConnectionResult.SUCCESS) {
213-
Log.i(TAG, "isGooglePlayServicesAvailable() returned: " + new ConnectionResult(code));
214-
Dialog dlg = GoogleApiAvailability.getInstance().getErrorDialog(this, code, RC_HANDLE_GMS);
215-
dlg.show();
216-
}
217-
218186
if (cameraSource != null) {
219187
try {
220-
cameraSourcePreview.start(cameraSource);
188+
cameraSource.start(cameraSourcePreview);
221189
} catch (IOException e) {
222190
Log.e(TAG, "Unable to start camera source.", e);
223191
cameraSource.release();
@@ -248,7 +216,7 @@ public void skipQrCodeCapture(View view) {
248216
* @param qrCode Detected QR code.
249217
*/
250218
@Override
251-
public void onQrCodeDetected(Barcode qrCode) {
219+
public void onQrCodeDetected(BarcodeReader.Result qrCode) {
252220
if (qrCode != null && !qrCodeSaved) {
253221
qrCodeSaved = true;
254222
QrCodeContentProcessor qrCodeContentProcessor = new QrCodeContentProcessor(this);
@@ -265,7 +233,10 @@ public void onQrCodeDetected(Barcode qrCode) {
265233
public void onQrCodeSaved(boolean status) {
266234
if (status) {
267235
Log.d(TAG, "Device parameters saved in external storage.");
268-
cameraSourcePreview.stop();
236+
if (cameraSource != null) {
237+
cameraSource.release();
238+
cameraSource = null;
239+
}
269240
nativeIncrementDeviceParamsChangedCount();
270241
finish();
271242
} else {

sdk/qrcode/android/java/com/google/cardboard/sdk/qrcode/QrCodeContentProcessor.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import android.content.Context;
1919
import android.util.Log;
2020
import android.widget.Toast;
21-
import com.google.android.gms.vision.barcode.Barcode;
21+
import zxingcpp.BarcodeReader;
2222
import com.google.cardboard.sdk.R;
2323

2424
/**
@@ -50,7 +50,7 @@ public interface Listener {
5050
* Application. It is used to write device params to scoped storage via {@code
5151
* Context.getFilesDir()}.
5252
*/
53-
public void processAndSaveQrCode(Barcode qrCode, Context context) {
53+
public void processAndSaveQrCode(BarcodeReader.Result qrCode, Context context) {
5454
new ProcessAndSaveQrCodeTask(context).execute(qrCode);
5555
}
5656

@@ -59,7 +59,7 @@ public void processAndSaveQrCode(Barcode qrCode, Context context) {
5959
* external storage.
6060
*/
6161
public class ProcessAndSaveQrCodeTask
62-
extends AsyncTask<Barcode, CardboardParamsUtils.UriToParamsStatus> {
62+
extends AsyncTask<BarcodeReader.Result, CardboardParamsUtils.UriToParamsStatus> {
6363
private final Context context;
6464

6565
/**
@@ -74,7 +74,7 @@ public ProcessAndSaveQrCodeTask(Context context) {
7474
}
7575

7676
@Override
77-
protected CardboardParamsUtils.UriToParamsStatus doInBackground(Barcode qrCode) {
77+
protected CardboardParamsUtils.UriToParamsStatus doInBackground(BarcodeReader.Result qrCode) {
7878
UrlFactory urlFactory = new UrlFactory();
7979
return getParamsFromQrCode(qrCode, urlFactory);
8080
}
@@ -109,13 +109,13 @@ protected void onPostExecute(CardboardParamsUtils.UriToParamsStatus result) {
109109
* @return Cardboard device parameters, or null if there is an error.
110110
*/
111111
private static CardboardParamsUtils.UriToParamsStatus getParamsFromQrCode(
112-
Barcode barcode, UrlFactory urlFactory) {
113-
if (barcode.valueFormat != Barcode.TEXT && barcode.valueFormat != Barcode.URL) {
114-
Log.e(TAG, "Invalid QR code format: " + barcode.valueFormat);
112+
BarcodeReader.Result barcode, UrlFactory urlFactory) {
113+
if (barcode.getText() == null) {
114+
Log.e(TAG, "Invalid QR code format: text is null");
115115
return CardboardParamsUtils.UriToParamsStatus.error(
116116
CardboardParamsUtils.UriToParamsStatus.STATUS_UNEXPECTED_FORMAT);
117117
}
118118

119-
return CardboardParamsUtils.getParamsFromUriString(barcode.rawValue, urlFactory);
119+
return CardboardParamsUtils.getParamsFromUriString(barcode.getText(), urlFactory);
120120
}
121121
}

sdk/qrcode/android/java/com/google/cardboard/sdk/qrcode/QrCodeTracker.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,42 @@
1515
*/
1616
package com.google.cardboard.sdk.qrcode;
1717

18-
import com.google.android.gms.vision.Tracker;
19-
import com.google.android.gms.vision.barcode.Barcode;
18+
import java.util.Arrays;
19+
import java.util.HashSet;
20+
import java.util.List;
21+
22+
import zxingcpp.BarcodeReader;
2023

2124
/**
2225
* QrCodeTracker is used for tracking or reading a QR code. This is used to receive newly detected
2326
* items, add a graphical representation to an overlay, update the graphics as the item changes, and
2427
* remove the graphics when the item goes away.
2528
*/
26-
public class QrCodeTracker extends Tracker<Barcode> {
29+
public class QrCodeTracker {
2730
private final Listener listener;
31+
private final HashSet<BarcodeReader.Result> lastData = new HashSet<>();
2832

2933
/**
3034
* Consume the item instance detected from an Activity or Fragment level by implementing the
3135
* Listener interface method onQrCodeDetected.
3236
*/
3337
public interface Listener {
34-
void onQrCodeDetected(Barcode qrCode);
38+
void onQrCodeDetected(BarcodeReader.Result qrCode);
3539
}
3640

37-
QrCodeTracker(Listener listener) {
41+
public QrCodeTracker(Listener listener) {
3842
this.listener = listener;
3943
}
4044

41-
/** Start tracking the detected item instance. */
42-
@Override
43-
public void onNewItem(int id, Barcode item) {
44-
if (item.displayValue != null) {
45-
listener.onQrCodeDetected(item);
45+
public void onItemsDetected(List<BarcodeReader.Result> data) {
46+
for (BarcodeReader.Result result : data) {
47+
if (lastData.stream().anyMatch(otherResult -> Arrays.equals(result.getBytes(), otherResult.getBytes()))) {
48+
// This QR code already was detected in last frame, it's not new.
49+
continue;
50+
}
51+
listener.onQrCodeDetected(result);
4652
}
53+
lastData.clear();
54+
lastData.addAll(data);
4755
}
4856
}

sdk/qrcode/android/java/com/google/cardboard/sdk/qrcode/QrCodeTrackerFactory.java

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)