diff --git a/play-services-vision-api/build.gradle b/play-services-vision-api/build.gradle
new file mode 100644
index 0000000..fa92012
--- /dev/null
+++ b/play-services-vision-api/build.gradle
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013-2015 microG Project Team
+ *
+ * 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.
+ */
+
+apply plugin: 'com.android.library'
+
+String getMyVersionName() {
+ def stdout = new ByteArrayOutputStream()
+ if (rootProject.file("gradlew").exists())
+ exec { commandLine 'git', 'describe', '--tags', '--always', '--dirty'; standardOutput = stdout }
+ else // automatic build system, don't tag dirty
+ exec { commandLine 'git', 'describe', '--tags', '--always'; standardOutput = stdout }
+ return stdout.toString().trim().substring(1)
+}
+
+group = 'org.microg'
+version = getMyVersionName()
+
+android {
+ compileSdkVersion androidCompileSdk()
+ buildToolsVersion "$androidBuildVersionTools"
+
+ aidlPackageWhiteList "com/google/android/gms/vision/BarcodeDetectorOptions.aidl"
+ aidlPackageWhiteList "com/google/android/gms/vision/INativeBarcodeDetectorCreator.aidl"
+ aidlPackageWhiteList "com/google/android/gms/vision/INativeBarcodeDetector.aidl"
+
+ defaultConfig {
+ versionName getMyVersionName()
+ minSdkVersion androidMinSdk()
+ targetSdkVersion androidTargetSdk()
+ }
+
+ testOptions {
+ unitTests {
+ includeAndroidResources = true
+ }
+ }
+
+ dependencies {
+ testImplementation 'junit:junit:4.12'
+ testImplementation "com.google.truth:truth:1.0.1"
+ testImplementation 'org.robolectric:robolectric:4.3'
+ }
+
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+ api project(':play-services-basement')
+ implementation 'com.google.zxing:android-core:3.3.0'
+ implementation 'com.google.zxing:core:3.3.0'
+}
diff --git a/play-services-vision-api/gradle.properties b/play-services-vision-api/gradle.properties
new file mode 100644
index 0000000..ac62dca
--- /dev/null
+++ b/play-services-vision-api/gradle.properties
@@ -0,0 +1,34 @@
+#
+# Copyright 2013-2016 microG Project Team
+#
+# 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.
+#
+
+POM_NAME=Play Services Internal Vision API
+POM_DESCRIPTION=Interfaces and objects for IPC between Play Services Library and Play Services Core
+
+POM_PACKAGING=aar
+
+POM_URL=https://github.com/microg/android_external_GmsApi
+
+POM_SCM_URL=https://github.com/microg/android_external_GmsApi
+POM_SCM_CONNECTION=scm:git@github.com:microg/android_external_GmsApi.git
+POM_SCM_DEV_CONNECTION=scm:git@github.com:microg/android_external_GmsApi.git
+
+POM_LICENCE_NAME=The Apache Software License, Version 2.0
+POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
+POM_LICENCE_DIST=repo
+
+POM_DEVELOPER_ID=mar-v-in
+POM_DEVELOPER_NAME=Marvin W
+
diff --git a/play-services-vision-api/src/main/AndroidManifest.xml b/play-services-vision-api/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..517ffd4
--- /dev/null
+++ b/play-services-vision-api/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+
+
+
+
diff --git a/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/Barcode.aidl b/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/Barcode.aidl
new file mode 100644
index 0000000..92a2274
--- /dev/null
+++ b/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/Barcode.aidl
@@ -0,0 +1,3 @@
+package com.google.android.gms.vision.barcode.internal.client;
+
+parcelable Barcode;
\ No newline at end of file
diff --git a/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/BarcodeDetectorOptions.aidl b/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/BarcodeDetectorOptions.aidl
new file mode 100644
index 0000000..bb2dfcd
--- /dev/null
+++ b/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/BarcodeDetectorOptions.aidl
@@ -0,0 +1,3 @@
+package com.google.android.gms.vision.barcode.internal.client;
+
+parcelable BarcodeDetectorOptions;
\ No newline at end of file
diff --git a/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/FrameMetadata.aidl b/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/FrameMetadata.aidl
new file mode 100644
index 0000000..76bf50e
--- /dev/null
+++ b/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/FrameMetadata.aidl
@@ -0,0 +1,3 @@
+package com.google.android.gms.vision.barcode.internal.client;
+
+parcelable FrameMetadata;
\ No newline at end of file
diff --git a/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/INativeBarcodeDetector.aidl b/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/INativeBarcodeDetector.aidl
new file mode 100644
index 0000000..948b923
--- /dev/null
+++ b/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/INativeBarcodeDetector.aidl
@@ -0,0 +1,13 @@
+package com.google.android.gms.vision.barcode.internal.client;
+
+import com.google.android.gms.vision.barcode.internal.client.BarcodeDetectorOptions;
+import com.google.android.gms.vision.barcode.internal.client.FrameMetadata;
+import com.google.android.gms.vision.barcode.internal.client.Barcode;
+import com.google.android.gms.dynamic.IObjectWrapper;
+
+interface INativeBarcodeDetector {
+ void unk0(IObjectWrapper byteBuffer, in FrameMetadata metadata) = 0;
+ Barcode[] detect(IObjectWrapper bitmap, in FrameMetadata metadata) = 1;
+ // Probably wraps a ByteBuffer? but is otherwise the same functionality?
+ void unk2(IObjectWrapper unk1, in FrameMetadata metadata) = 2;
+}
\ No newline at end of file
diff --git a/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/INativeBarcodeDetectorCreator.aidl b/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/INativeBarcodeDetectorCreator.aidl
new file mode 100644
index 0000000..3f67b93
--- /dev/null
+++ b/play-services-vision-api/src/main/aidl/com/google/android/gms/vision/barcode/internal/client/INativeBarcodeDetectorCreator.aidl
@@ -0,0 +1,10 @@
+package com.google.android.gms.vision.barcode.internal.client;
+
+import com.google.android.gms.vision.barcode.internal.client.BarcodeDetectorOptions;
+import com.google.android.gms.vision.barcode.internal.client.INativeBarcodeDetector;
+import com.google.android.gms.dynamic.IObjectWrapper;
+
+interface INativeBarcodeDetectorCreator {
+ // unk is probably Context
+ INativeBarcodeDetector create(IObjectWrapper unk, in BarcodeDetectorOptions options) = 0;
+}
diff --git a/play-services-vision-api/src/main/java/com/google/android/gms/vision/barcode/internal/client/Barcode.java b/play-services-vision-api/src/main/java/com/google/android/gms/vision/barcode/internal/client/Barcode.java
new file mode 100644
index 0000000..f694bc1
--- /dev/null
+++ b/play-services-vision-api/src/main/java/com/google/android/gms/vision/barcode/internal/client/Barcode.java
@@ -0,0 +1,447 @@
+package com.google.android.gms.vision.barcode.internal.client;
+
+import android.graphics.Point;
+import android.util.Log;
+import android.util.Patterns;
+
+import org.microg.safeparcel.AutoSafeParcelable;
+import org.microg.safeparcel.SafeParceled;
+
+import java.net.MalformedURLException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.net.URL;
+
+/**
+ * Represents a barcode that can be detected by the com.google.android.gms.vision.barcode and
+ * com.google.firebase.ml.vision.barcode packages.
+ *
+ * Not all types are supported yet.
+ */
+public class Barcode extends AutoSafeParcelable {
+ public static final String TAG = Barcode.class.getSimpleName();
+
+ public static final int CONTACT_INFO = 1;
+ public static final int EMAIL = 2;
+ public static final int ISBN = 3;
+ public static final int PHONE = 4;
+ public static final int PRODUCT = 5;
+ public static final int SMS = 6;
+ public static final int TEXT = 7;
+ public static final int URL = 8;
+ public static final int WIFI = 9;
+ public static final int GEO = 10;
+ public static final int CALENDAR_EVENT = 11;
+ public static final int DRIVER_LICENSE = 12;
+
+ @SafeParceled(1)
+ private final int versionCode = 1;
+ @SafeParceled(2)
+ public int format;
+ @SafeParceled(3)
+ public String rawValue;
+ @SafeParceled(4)
+ public String displayValue;
+ @SafeParceled(5)
+ public int valueFormat;
+ @SafeParceled(6)
+ public Point[] cornerPoints;
+ @SafeParceled(7)
+ public Barcode.Email email;
+ @SafeParceled(8)
+ public Barcode.Phone phone;
+ @SafeParceled(9)
+ public Barcode.Sms sms;
+ @SafeParceled(10)
+ public Barcode.WiFi wifi;
+ @SafeParceled(11)
+ public Barcode.UrlBookmark url;
+ @SafeParceled(12)
+ public Barcode.GeoPoint geoPoint;
+ @SafeParceled(13)
+ public Barcode.CalendarEvent calendarEvent;
+ @SafeParceled(14)
+ public Barcode.ContactInfo contactInfo;
+ @SafeParceled(15)
+ public Barcode.DriverLicense driverLicense;
+
+ public Barcode() {}
+
+ public Barcode(String rawValue) {
+ this.valueFormat = TEXT;
+ this.rawValue = rawValue;
+ this.displayValue = rawValue;
+ detectAndSetType();
+ }
+
+ public Barcode(String rawValue, Point[] points) {
+ this(rawValue);
+ this.cornerPoints = points;
+ }
+
+ // TODO: Not all types are handled yet, calendarEvent, contactInfo, and driverLicense are not supported yet.
+ private void detectAndSetType() {
+ try {
+ // We do this for the side effect when the URI is invalid
+ new URL(this.rawValue);
+ this.valueFormat = Barcode.URL;
+ this.url = new Barcode.UrlBookmark();
+ this.url.url = this.rawValue;
+ return;
+ } catch(MalformedURLException _ex) {}
+
+ int schemeSeparatorIndex = this.rawValue.indexOf(":");
+ if (schemeSeparatorIndex == -1 || this.rawValue.length() == schemeSeparatorIndex + 1) {
+ this.valueFormat = Barcode.TEXT;
+ return;
+ }
+ String scheme = this.rawValue.substring(0, schemeSeparatorIndex);
+ String rest = this.rawValue.substring(schemeSeparatorIndex + 1);
+ switch(scheme.toLowerCase()) {
+ case "mailto":
+ this.valueFormat = Barcode.EMAIL;
+ this.email = Barcode.Email.parse(rest);
+ break;
+ case "tel":
+ this.valueFormat = Barcode.PHONE;
+ this.phone = Barcode.Phone.parse(rest);
+ break;
+ case "sms":
+ case "smsto":
+ this.valueFormat = Barcode.SMS;
+ this.sms = Barcode.Sms.parse(rest);
+ break;
+ case "mebkm":
+ this.valueFormat = Barcode.URL;
+ this.url = Barcode.UrlBookmark.parse(rest);
+ break;
+ case "wifi":
+ this.valueFormat = Barcode.WIFI;
+ this.wifi = Barcode.WiFi.parse(rest);
+ break;
+ case "geo":
+ this.geoPoint = Barcode.GeoPoint.parse(rest);
+ if (this.geoPoint != null) {
+ this.valueFormat = Barcode.GEO;
+ }
+ break;
+ default:
+ Log.d(TAG, "Scheme " + scheme + " is not supported yet");
+ this.valueFormat = Barcode.TEXT;
+ break;
+ }
+ }
+
+
+ private static String extractMatch(String value, Pattern pattern, int group) {
+ Matcher match = pattern.matcher(value);
+ if (!match.matches() || match.group(group) == null) {
+ return null;
+ }
+ return match.group(group);
+ }
+
+ private static String extractMatch(String value, Pattern pattern) {
+ return extractMatch(value, pattern, 0);
+ }
+
+ public static Creator CREATOR = new AutoCreator<>(Barcode.class);
+
+ public static class Email extends AutoSafeParcelable {
+ public static final int UNKNOWN = 0;
+ public static final int WORK = 1;
+ public static final int HOME = 2;
+
+ @SafeParceled(1)
+ final int versionCode = 1;
+ @SafeParceled(2)
+ public int type;
+ @SafeParceled(3)
+ public String address;
+ @SafeParceled(4)
+ public String subject;
+ @SafeParceled(5)
+ public String body;
+
+ public static Email parse(String value) {
+ Email result = new Email();
+ result.type = UNKNOWN;
+
+ int index = value.indexOf('?');
+ if (index != -1) {
+ result.address = extractMatch(value.substring(0, index), Patterns.EMAIL_ADDRESS);
+ } else {
+ result.address = extractMatch(value, Patterns.EMAIL_ADDRESS);
+ }
+ result.subject = extractMatch(value, Pattern.compile(".*?subject=([^&]*).*"), 1);
+ result.body = extractMatch(value, Pattern.compile(".*?body=([^&]*).*"), 1);
+ return result;
+ }
+
+ public static Creator CREATOR = new AutoCreator<>(Email.class);
+ }
+
+ public static class Phone extends AutoSafeParcelable {
+ public static final int UNKNOWN = 0;
+ public static final int WORK = 1;
+ public static final int HOME = 2;
+ public static final int FAX = 3;
+ public static final int MOBILE = 4;
+
+ @SafeParceled(1)
+ final int versionCode = 1;
+ @SafeParceled(2)
+ public int type;
+ @SafeParceled(3)
+ public String number;
+
+ public static Phone parse(String value) {
+ Phone phone = new Phone();
+ phone.type = UNKNOWN;
+ phone.number = value;
+ return phone;
+ }
+
+ public static Creator CREATOR = new AutoCreator<>(Phone.class);
+ }
+
+ public static class Sms extends AutoSafeParcelable {
+ @SafeParceled(1)
+ final int versionCode = 1;
+ @SafeParceled(2)
+ public String message;
+ @SafeParceled(3)
+ public String phoneNumber;
+
+ public static Sms parse(String value) {
+ Sms result = new Sms();
+ result.phoneNumber = extractMatch(value, Pattern.compile("([+]?[0-9]+).*"), 1);
+ result.message = extractMatch(value, Pattern.compile(".*?(body=|:|,)(.*)$"), 2);
+ return result;
+ }
+
+ public static Creator CREATOR = new AutoCreator<>(Sms.class);
+ }
+
+ public static class WiFi extends AutoSafeParcelable {
+ public static final int OPEN = 1;
+ public static final int WPA = 2;
+ public static final int WEP = 3;
+
+ @SafeParceled(1)
+ final int versionCode = 1;
+ @SafeParceled(2)
+ public String ssid;
+ @SafeParceled(3)
+ public String password;
+ @SafeParceled(4)
+ public int encryptionType;
+
+ public static WiFi parse(String value) {
+ WiFi result = new WiFi();
+ result.password = extractMatch(value, Pattern.compile(".*?(P|p):(.*?);.*"), 2);
+ result.ssid = extractMatch(value, Pattern.compile(".*?(S|s):(.*?);.*"), 2);
+ result.encryptionType = OPEN;
+ String encryptionType = extractMatch(value, Pattern.compile(".*?(T|t):(.*?);.*"), 2);
+ switch (encryptionType.toLowerCase()) {
+ case "wpa":
+ result.encryptionType = WPA;
+ break;
+ case "wep":
+ result.encryptionType = WEP;
+ break;
+ }
+ return result;
+ }
+
+ public static Creator CREATOR = new AutoCreator<>(WiFi.class);
+ }
+
+ public static class UrlBookmark extends AutoSafeParcelable {
+ @SafeParceled(1)
+ final int versionCode = 1;
+ @SafeParceled(2)
+ public String title;
+ @SafeParceled(3)
+ public String url;
+
+ public static UrlBookmark parse(String value) {
+ UrlBookmark result = new UrlBookmark();
+ result.title = extractMatch(value, Pattern.compile(".*?(TITLE|title):(.*?);.*"), 2);
+ result.url = extractMatch(value, Pattern.compile(".*?(URL|url):(.*?);.*"), 2);
+ return result;
+ }
+
+ public static Creator CREATOR = new AutoCreator<>(UrlBookmark.class);
+ }
+
+ public static class GeoPoint extends AutoSafeParcelable {
+ @SafeParceled(1)
+ final int versionCode = 1;
+ @SafeParceled(2)
+ public double lat;
+ @SafeParceled(3)
+ public double lng;
+
+ public GeoPoint(double lat, double lng) {
+ this.lat = lat;
+ this.lng = lng;
+ }
+
+ public GeoPoint() {}
+
+ public static GeoPoint parse(String value) {
+ GeoPoint result = new GeoPoint();
+ int commaIndex = value.indexOf(',');
+ // We need at least one number after the comma for longitude
+ if (commaIndex == -1 || value.length() <= commaIndex) {
+ return null;
+ }
+ try {
+ result.lat = Double.parseDouble(value.substring(0, commaIndex));
+ result.lng = Double.parseDouble(value.substring(commaIndex + 1));
+ } catch(NumberFormatException e) {
+ return null;
+ }
+ return result;
+ }
+
+ public static Creator CREATOR = new AutoCreator<>(GeoPoint.class);
+ }
+
+ public static class CalendarDateTime extends AutoSafeParcelable {
+ @SafeParceled(1)
+ final int versionCode = 1;
+ @SafeParceled(2)
+ public int year;
+ @SafeParceled(3)
+ public int month;
+ @SafeParceled(4)
+ public int day;
+ @SafeParceled(5)
+ public int hours;
+ @SafeParceled(6)
+ public int minutes;
+ @SafeParceled(7)
+ public int seconds;
+ @SafeParceled(8)
+ public boolean isUtc;
+ @SafeParceled(9)
+ public String rawValue;
+
+ public static Creator CREATOR = new AutoCreator<>(CalendarDateTime.class);
+ }
+
+ public static class CalendarEvent extends AutoSafeParcelable {
+ @SafeParceled(1)
+ final int versionCode = 1;
+ @SafeParceled(2)
+ public String summary;
+ @SafeParceled(3)
+ public String description;
+ @SafeParceled(4)
+ public String location;
+ @SafeParceled(5)
+ public String organizer;
+ @SafeParceled(6)
+ public String status;
+ @SafeParceled(7)
+ public Barcode.CalendarDateTime start;
+ @SafeParceled(8)
+ public Barcode.CalendarDateTime end;
+
+ public static Creator CREATOR = new AutoCreator<>(CalendarEvent.class);
+ }
+
+ public static class PersonName extends AutoSafeParcelable {
+ @SafeParceled(1)
+ final int versionCode = 1;
+ @SafeParceled(2)
+ public String formattedName;
+ @SafeParceled(3)
+ public String pronunciation;
+ @SafeParceled(4)
+ public String prefix;
+ @SafeParceled(5)
+ public String first;
+ @SafeParceled(6)
+ public String middle;
+ @SafeParceled(7)
+ public String last;
+ @SafeParceled(8)
+ public String suffix;
+
+ public static Creator CREATOR = new AutoCreator<>(PersonName.class);
+ }
+
+ public static class Address extends AutoSafeParcelable {
+ public static final int UNKNOWN = 0;
+ public static final int WORK = 1;
+ public static final int HOME = 2;
+
+ @SafeParceled(1)
+ final int versionCode = 1;
+ @SafeParceled(2)
+ public int type;
+ @SafeParceled(3)
+ public String[] addressLines;
+
+ public static Creator CREATOR = new AutoCreator<>(Address.class);
+ }
+
+ public static class DriverLicense extends AutoSafeParcelable {
+ @SafeParceled(1)
+ final int versionCode = 1;
+ @SafeParceled(2)
+ public String documentType;
+ @SafeParceled(3)
+ public String firstName;
+ @SafeParceled(4)
+ public String middleName;
+ @SafeParceled(5)
+ public String lastName;
+ @SafeParceled(6)
+ public String gender;
+ @SafeParceled(7)
+ public String addressStreet;
+ @SafeParceled(8)
+ public String addressCity;
+ @SafeParceled(9)
+ public String addressState;
+ @SafeParceled(10)
+ public String addressZip;
+ @SafeParceled(11)
+ public String licenseNumber;
+ @SafeParceled(12)
+ public String issueDate;
+ @SafeParceled(13)
+ public String expiryDate;
+ @SafeParceled(14)
+ public String birthDate;
+ @SafeParceled(15)
+ public String issuingCountry;
+
+ public static Creator CREATOR = new AutoCreator<>(DriverLicense.class);
+ }
+
+ public static class ContactInfo extends AutoSafeParcelable {
+ @SafeParceled(1)
+ final int versionCode = 1;
+ @SafeParceled(2)
+ public Barcode.PersonName name;
+ @SafeParceled(3)
+ public String organization;
+ @SafeParceled(4)
+ public String title;
+ @SafeParceled(5)
+ public Barcode.Phone[] phones;
+ @SafeParceled(6)
+ public Barcode.Email[] emails;
+ @SafeParceled(7)
+ public String[] urls;
+ @SafeParceled(8)
+ public Barcode.Address[] addresses;
+
+ public static Creator CREATOR = new AutoCreator<>(ContactInfo.class);
+ }
+}
\ No newline at end of file
diff --git a/play-services-vision-api/src/main/java/com/google/android/gms/vision/barcode/internal/client/BarcodeDetector.java b/play-services-vision-api/src/main/java/com/google/android/gms/vision/barcode/internal/client/BarcodeDetector.java
new file mode 100644
index 0000000..74b357e
--- /dev/null
+++ b/play-services-vision-api/src/main/java/com/google/android/gms/vision/barcode/internal/client/BarcodeDetector.java
@@ -0,0 +1,146 @@
+package com.google.android.gms.vision.barcode.internal.client;
+
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.android.gms.dynamic.IObjectWrapper;
+import com.google.android.gms.dynamic.ObjectWrapper;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.ChecksumException;
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.Reader;
+import com.google.zxing.Result;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.HybridBinarizer;
+import com.google.zxing.datamatrix.DataMatrixReader;
+import com.google.zxing.oned.CodaBarReader;
+import com.google.zxing.oned.Code128Reader;
+import com.google.zxing.oned.Code39Reader;
+import com.google.zxing.oned.Code93Reader;
+import com.google.zxing.oned.EAN13Reader;
+import com.google.zxing.oned.EAN8Reader;
+import com.google.zxing.oned.ITFReader;
+import com.google.zxing.oned.UPCAReader;
+import com.google.zxing.oned.UPCEReader;
+import com.google.zxing.pdf417.PDF417Reader;
+import com.google.zxing.qrcode.QRCodeReader;
+import com.google.zxing.RGBLuminanceSource;
+
+public class BarcodeDetector extends INativeBarcodeDetector.Stub {
+ public static final String TAG = BarcodeDetector.class.getSimpleName();
+
+ // TODO: Can we force these to be a bitfield somehow?
+ public static final int CODE_128 = 1;
+ public static final int CODE_39 = 2;
+ public static final int CODE_93 = 4;
+ public static final int CODABAR = 8;
+ public static final int DATA_MATRIX = 16;
+ public static final int EAN_13 = 32;
+ public static final int EAN_8 = 64;
+ public static final int ITF = 128;
+ public static final int QR_CODE = 256;
+ public static final int UPC_A = 512;
+ public static final int UPC_E = 1024;
+ public static final int PDF417 = 2048;
+
+ private int formats;
+
+ public BarcodeDetector(BarcodeDetectorOptions options) {
+ this.formats = options.formats;
+ }
+
+ @Override
+ public void unk0(IObjectWrapper byteBuffer, FrameMetadata metadata) throws RemoteException {
+ Log.i(TAG, "Unkown method 0 called");
+ }
+
+ @Override
+ public Barcode[] detect(IObjectWrapper wrappedBitmap, FrameMetadata metadata) throws RemoteException {
+ Bitmap bitmap = ObjectWrapper.unwrapTyped(wrappedBitmap, Bitmap.class);
+ if (bitmap == null) {
+ Log.e(TAG, "Could not unwrap Bitmap");
+ return null;
+ }
+
+ IntBuffer frameBuf = IntBuffer.allocate(bitmap.getByteCount());
+ bitmap.copyPixelsToBuffer(frameBuf);
+ int[] frameBytes = frameBuf.array();
+
+ RGBLuminanceSource source = new RGBLuminanceSource(bitmap.getHeight(), bitmap.getWidth(), frameBytes);
+ HybridBinarizer binarizer = new HybridBinarizer(source);
+ BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
+ List barcodes = new ArrayList<>();
+
+ if ((this.formats & CODE_128) != 0) {
+ tryDetect(new Code128Reader(), binaryBitmap, barcodes);
+ }
+ if ((this.formats & CODE_39) != 0) {
+ tryDetect(new Code39Reader(), binaryBitmap, barcodes);
+ }
+ if ((this.formats & CODE_93) != 0) {
+ tryDetect(new Code93Reader(), binaryBitmap, barcodes);
+ }
+ if ((this.formats & CODABAR) != 0) {
+ tryDetect(new CodaBarReader(), binaryBitmap, barcodes);
+ }
+ if ((this.formats & DATA_MATRIX) != 0) {
+ tryDetect(new DataMatrixReader(), binaryBitmap, barcodes);
+ }
+ if ((this.formats & EAN_13) != 0) {
+ tryDetect(new EAN13Reader(), binaryBitmap, barcodes);
+ }
+ if ((this.formats & EAN_8) != 0) {
+ tryDetect(new EAN8Reader(), binaryBitmap, barcodes);
+ }
+ if ((this.formats & ITF) != 0) {
+ tryDetect(new ITFReader(), binaryBitmap, barcodes);
+ }
+ if ((this.formats & QR_CODE) != 0) {
+ tryDetect(new QRCodeReader(), binaryBitmap, barcodes);
+ }
+ if ((this.formats & UPC_A) != 0) {
+ tryDetect(new UPCAReader(), binaryBitmap, barcodes);
+ }
+ if ((this.formats & UPC_E) != 0) {
+ tryDetect(new UPCEReader(), binaryBitmap, barcodes);
+ }
+ if ((this.formats & PDF417) != 0) {
+ tryDetect(new PDF417Reader(), binaryBitmap, barcodes);
+ }
+
+ return barcodes.toArray(new Barcode[0]);
+ }
+
+ @Override
+ public void unk2(IObjectWrapper unk1, FrameMetadata metadata) throws RemoteException {
+ Log.d(TAG, "Unknown 2 called");
+ }
+
+ private void tryDetect(Reader reader, BinaryBitmap bitmap, List results) {
+ try {
+ Result result = reader.decode(bitmap);
+ if (result.getText() != null) {
+ // Try to fill in the corner information, not sure that this is correct.
+ List corners = new ArrayList<>();
+ for (ResultPoint point : result.getResultPoints()) {
+ corners.add(new Point((int)point.getX(), (int)point.getY()));
+ }
+ results.add(new Barcode(result.getText(), corners.toArray(new Point[]{})));
+ }
+ } catch (FormatException e) {
+ e.printStackTrace();
+ } catch (ChecksumException e) {
+ e.printStackTrace();
+ } catch (NotFoundException e) {
+ Log.d(TAG, "No barcode found when detecting with " + reader.getClass().getName());
+ }
+ }
+}
diff --git a/play-services-vision-api/src/main/java/com/google/android/gms/vision/barcode/internal/client/BarcodeDetectorOptions.java b/play-services-vision-api/src/main/java/com/google/android/gms/vision/barcode/internal/client/BarcodeDetectorOptions.java
new file mode 100644
index 0000000..c66bb8f
--- /dev/null
+++ b/play-services-vision-api/src/main/java/com/google/android/gms/vision/barcode/internal/client/BarcodeDetectorOptions.java
@@ -0,0 +1,14 @@
+package com.google.android.gms.vision.barcode.internal.client;
+
+import org.microg.safeparcel.AutoSafeParcelable;
+import org.microg.safeparcel.SafeParceled;
+
+public class BarcodeDetectorOptions extends AutoSafeParcelable {
+ @SafeParceled(1)
+ public int versionCode = 1;
+ @SafeParceled(2)
+ public int formats;
+
+ public static Creator CREATOR = new AutoCreator(BarcodeDetectorOptions.class);
+
+}
diff --git a/play-services-vision-api/src/main/java/com/google/android/gms/vision/barcode/internal/client/FrameMetadata.java b/play-services-vision-api/src/main/java/com/google/android/gms/vision/barcode/internal/client/FrameMetadata.java
new file mode 100644
index 0000000..90364d2
--- /dev/null
+++ b/play-services-vision-api/src/main/java/com/google/android/gms/vision/barcode/internal/client/FrameMetadata.java
@@ -0,0 +1,21 @@
+package com.google.android.gms.vision.barcode.internal.client;
+
+import org.microg.safeparcel.AutoSafeParcelable;
+import org.microg.safeparcel.SafeParceled;
+
+public class FrameMetadata extends AutoSafeParcelable {
+ @SafeParceled(1)
+ private final int versionCode = 1;
+ @SafeParceled(2)
+ public int width;
+ @SafeParceled(3)
+ public int height;
+ @SafeParceled(4)
+ public int id;
+ @SafeParceled(5)
+ public long timestampMillis;
+ @SafeParceled(6)
+ public int rotation;
+
+ public static Creator CREATOR = new AutoCreator(FrameMetadata.class);
+}
diff --git a/play-services-vision-api/src/test/java/BarcodeTypesTest.java b/play-services-vision-api/src/test/java/BarcodeTypesTest.java
new file mode 100644
index 0000000..4954b37
--- /dev/null
+++ b/play-services-vision-api/src/test/java/BarcodeTypesTest.java
@@ -0,0 +1,161 @@
+import com.google.android.gms.vision.barcode.internal.client.Barcode;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+/**
+ * Verifies that we make a reasonable effort to decode the different barcode types that google
+ * defines. Most of the barcode data comes from these sites:
+ * * http://goqr.me/
+ * * https://github.com/zxing/zxing/wiki/Barcode-Contents
+ * * https://github.com/codebude/QRCoder/wiki/Advanced-usage---Payload-generators
+ */
+@RunWith(RobolectricTestRunner.class)
+public class BarcodeTypesTest {
+ @Test
+ public void geoPoints() {
+ Barcode barcode = new Barcode("geo:12.334,13.337");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.GEO);
+ assertThat(barcode.rawValue).isEqualTo("geo:12.334,13.337");
+ assertThat(barcode.geoPoint.lat).isEqualTo(12.334);
+ assertThat(barcode.geoPoint.lng).isEqualTo(13.337);
+
+ barcode = new Barcode("geo:12.no,13.yes");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.TEXT);
+ assertThat(barcode.rawValue).isEqualTo("geo:12.no,13.yes");
+ assertThat(barcode.geoPoint).isNull();
+ }
+
+ @Test
+ public void phoneNumber() {
+ Barcode barcode = new Barcode("tel:+12387784");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.PHONE);
+ assertThat(barcode.phone).isNotNull();
+ assertThat(barcode.phone.number).isEqualTo("+12387784");
+ assertThat(barcode.phone.type).isEqualTo(Barcode.Phone.UNKNOWN);
+
+ barcode = new Barcode("tel:+2763test");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.PHONE);
+ assertThat(barcode.phone).isNotNull();
+ assertThat(barcode.phone.number).isEqualTo("+2763test");
+ assertThat(barcode.phone.type).isEqualTo(Barcode.Phone.UNKNOWN);
+ }
+
+ @Test
+ public void mail() {
+ Barcode barcode = new Barcode("mailto:test@lel.com");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.EMAIL);
+ assertThat(barcode.email).isNotNull();
+ assertThat(barcode.email.address).isEqualTo("test@lel.com");
+ assertThat(barcode.email.type).isEqualTo(Barcode.Email.UNKNOWN);
+ assertThat(barcode.email.body).isNull();
+ assertThat(barcode.email.subject).isNull();
+
+ barcode = new Barcode("mailto:test@lel.com?subject=hello");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.EMAIL);
+ assertThat(barcode.email).isNotNull();
+ assertThat(barcode.email.address).isEqualTo("test@lel.com");
+ assertThat(barcode.email.type).isEqualTo(Barcode.Email.UNKNOWN);
+ assertThat(barcode.email.body).isNull();
+ assertThat(barcode.email.subject).isEqualTo("hello");
+
+ barcode = new Barcode("mailto:test@lel.com?body=have a good day");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.EMAIL);
+ assertThat(barcode.email).isNotNull();
+ assertThat(barcode.email.address).isEqualTo("test@lel.com");
+ assertThat(barcode.email.type).isEqualTo(Barcode.Email.UNKNOWN);
+ assertThat(barcode.email.body).isEqualTo("have a good day");
+ assertThat(barcode.email.subject).isNull();
+
+ barcode = new Barcode("mailto:test@lel.com?body=have a good day&subject=this is a header");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.EMAIL);
+ assertThat(barcode.email).isNotNull();
+ assertThat(barcode.email.address).isEqualTo("test@lel.com");
+ assertThat(barcode.email.type).isEqualTo(Barcode.Email.UNKNOWN);
+ assertThat(barcode.email.body).isEqualTo("have a good day");
+ assertThat(barcode.email.subject).isEqualTo("this is a header");
+ }
+
+ @Test
+ public void sms() {
+ Barcode barcode = new Barcode("sms:+91827454");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.SMS);
+ assertThat(barcode.sms).isNotNull();
+ assertThat(barcode.sms.phoneNumber).isEqualTo("+91827454");
+ assertThat(barcode.sms.message).isNull();
+
+ barcode = new Barcode("sms:91827454?body=have a good day");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.SMS);
+ assertThat(barcode.sms).isNotNull();
+ assertThat(barcode.sms.phoneNumber).isEqualTo("91827454");
+ assertThat(barcode.sms.message).isEqualTo("have a good day");
+
+ barcode = new Barcode("sms:91827454,have a good day");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.SMS);
+ assertThat(barcode.sms).isNotNull();
+ assertThat(barcode.sms.phoneNumber).isEqualTo("91827454");
+ assertThat(barcode.sms.message).isEqualTo("have a good day");
+
+ barcode = new Barcode("smsto:91827454:have a good day");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.SMS);
+ assertThat(barcode.sms).isNotNull();
+ assertThat(barcode.sms.phoneNumber).isEqualTo("91827454");
+ assertThat(barcode.sms.message).isEqualTo("have a good day");
+ }
+
+ @Test
+ public void wifi() {
+ Barcode barcode = new Barcode("wifi:T:wpa;S:network;P:password;;");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.WIFI);
+ assertThat(barcode.wifi).isNotNull();
+ assertThat(barcode.wifi.encryptionType).isEqualTo(Barcode.WiFi.WPA);
+ assertThat(barcode.wifi.password).isEqualTo("password");
+ assertThat(barcode.wifi.ssid).isEqualTo("network");
+
+ barcode = new Barcode("wifi:T:WEP;S:network;P:password;;");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.WIFI);
+ assertThat(barcode.wifi).isNotNull();
+ assertThat(barcode.wifi.encryptionType).isEqualTo(Barcode.WiFi.WEP);
+
+ barcode = new Barcode("wifi:T:nopass;S:network;P:password;;");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.WIFI);
+ assertThat(barcode.wifi).isNotNull();
+ assertThat(barcode.wifi.encryptionType).isEqualTo(Barcode.WiFi.OPEN);
+
+ barcode = new Barcode("wifi:T:dummy;S:network;P:password;;");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.WIFI);
+ assertThat(barcode.wifi).isNotNull();
+ assertThat(barcode.wifi.encryptionType).isEqualTo(Barcode.WiFi.OPEN);
+ }
+
+ @Test
+ public void bookmark() {
+ Barcode barcode = new Barcode("http://lel.se");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.URL);
+ assertThat(barcode.url).isNotNull();
+ assertThat(barcode.url.url).isEqualTo("http://lel.se");
+ assertThat(barcode.url.title).isNull();
+
+ barcode = new Barcode("mebkm:title:hello;url:http://lel.se;;");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.URL);
+ assertThat(barcode.url).isNotNull();
+ assertThat(barcode.url.url).isEqualTo("http://lel.se");
+ assertThat(barcode.url.title).isEqualTo("hello");
+
+ barcode = new Barcode("mebkm:title:hello;url:http://lel.se;");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.URL);
+ assertThat(barcode.url).isNotNull();
+ assertThat(barcode.url.url).isEqualTo("http://lel.se");
+ assertThat(barcode.url.title).isEqualTo("hello");
+
+ barcode = new Barcode("mebkm:url:http://lel.se;");
+ assertThat(barcode.valueFormat).isEqualTo(Barcode.URL);
+ assertThat(barcode.url).isNotNull();
+ assertThat(barcode.url.url).isEqualTo("http://lel.se");
+ assertThat(barcode.url.title).isNull();
+ }
+}
\ No newline at end of file
diff --git a/play-services-vision-api/src/test/resources/robolectric.properties b/play-services-vision-api/src/test/resources/robolectric.properties
new file mode 100644
index 0000000..89a6c8b
--- /dev/null
+++ b/play-services-vision-api/src/test/resources/robolectric.properties
@@ -0,0 +1 @@
+sdk=28
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index a625f04..245021a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -5,3 +5,4 @@ include ':play-services-cast-framework-api'
include ':play-services-iid-api'
include ':play-services-location-api'
include ':play-services-wearable-api'
+include ':play-services-vision-api'