Explorar o código

feat: 源码方式接入zxing

DoggyZhang hai 4 meses
pai
achega
28150fac8b
Modificáronse 74 ficheiros con 8231 adicións e 9 borrados
  1. 5 0
      README.md
  2. 1 0
      app/build.gradle
  3. 7 0
      app/src/main/AndroidManifest.xml
  4. 5 0
      app/src/main/java/com/adealink/frame/MainActivity.kt
  5. 91 0
      app/src/main/java/com/adealink/frame/zxing/ZXingActivity.kt
  6. 11 0
      app/src/main/res/layout/activity_main.xml
  7. 47 0
      app/src/main/res/layout/activity_zxing.xml
  8. 1 0
      external/zxing/.gitignore
  9. 67 0
      external/zxing/build.gradle
  10. 41 0
      external/zxing/src/AndroidManifest.xml
  11. 93 0
      external/zxing/src/main/java/com/google/zxing/client/android/AmbientLightManager.java
  12. 138 0
      external/zxing/src/main/java/com/google/zxing/client/android/BeepManager.java
  13. 99 0
      external/zxing/src/main/java/com/google/zxing/client/android/DecodeFormatManager.java
  14. 236 0
      external/zxing/src/main/java/com/google/zxing/client/android/DecodeHintManager.java
  15. 115 0
      external/zxing/src/main/java/com/google/zxing/client/android/InactivityTimer.java
  16. 228 0
      external/zxing/src/main/java/com/google/zxing/client/android/Intents.java
  17. 86 0
      external/zxing/src/main/java/com/google/zxing/client/android/camera/open/OpenCameraInterface.java
  18. 432 0
      external/zxing/src/main/java/com/google/zxing/integration/android/IntentIntegrator.java
  19. 120 0
      external/zxing/src/main/java/com/google/zxing/integration/android/IntentResult.java
  20. 31 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/BarcodeCallback.java
  21. 73 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/BarcodeEncoder.java
  22. 176 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/BarcodeResult.java
  23. 208 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/BarcodeView.java
  24. 885 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/CameraPreview.java
  25. 72 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/CaptureActivity.java
  26. 455 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/CaptureManager.java
  27. 21 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/CompoundBarcodeView.java
  28. 104 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/Decoder.java
  29. 24 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/DecoderFactory.java
  30. 33 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/DecoderResultPointCallback.java
  31. 173 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/DecoderThread.java
  32. 306 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/DecoratedBarcodeView.java
  33. 72 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/DefaultDecoderFactory.java
  34. 36 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/InvertedDecoder.java
  35. 43 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/MixedDecoder.java
  36. 142 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/RawImageData.java
  37. 13 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/RotationCallback.java
  38. 67 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/RotationListener.java
  39. 21 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/ScanContract.java
  40. 155 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/ScanIntentResult.java
  41. 262 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/ScanOptions.java
  42. 117 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/Size.java
  43. 178 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/SourceData.java
  44. 14 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/Util.java
  45. 259 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/ViewfinderView.java
  46. 134 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/AutoFocusManager.java
  47. 366 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraConfigurationUtils.java
  48. 289 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraInstance.java
  49. 522 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraManager.java
  50. 17 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraParametersCallback.java
  51. 163 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraSettings.java
  52. 48 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraSurface.java
  53. 110 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraThread.java
  54. 78 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CenterCropStrategy.java
  55. 98 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/DisplayConfiguration.java
  56. 78 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/FitCenterStrategy.java
  57. 62 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/FitXYStrategy.java
  58. 155 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/LegacyPreviewScalingStrategy.java
  59. 11 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/PreviewCallback.java
  60. 96 0
      external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/PreviewScalingStrategy.java
  61. 21 0
      external/zxing/src/main/res/layout/zxing_barcode_scanner.xml
  62. 30 0
      external/zxing/src/main/res/layout/zxing_capture.xml
  63. BIN=BIN
      external/zxing/src/main/res/raw/zxing_beep.ogg
  64. 22 0
      external/zxing/src/main/res/values-id/zxing_strings.xml
  65. 22 0
      external/zxing/src/main/res/values-zh/zxing_strings.xml
  66. 20 0
      external/zxing/src/main/res/values/zxing_about_library_strings.xml
  67. 27 0
      external/zxing/src/main/res/values/zxing_attrs.xml
  68. 29 0
      external/zxing/src/main/res/values/zxing_colors.xml
  69. 27 0
      external/zxing/src/main/res/values/zxing_ids.xml
  70. 22 0
      external/zxing/src/main/res/values/zxing_strings.xml
  71. 5 0
      external/zxing/src/main/res/values/zxing_themes.xml
  72. 1 1
      frame/bom/build.gradle
  73. 6 0
      gradle/libs.versions.toml
  74. 9 8
      settings.gradle

+ 5 - 0
README.md

@@ -130,6 +130,11 @@
 ./gradlew :external:SVGAPlayer:clean
 ./gradlew :external:SVGAPlayer:publish
 ```
+- zxing: zxing二维码
+```shell
+./gradlew :external:zxing:clean
+./gradlew :external:zxing:publish
+```
 - game: 游戏接入框架
 ```shell
 ./gradlew :frame:game:clean

+ 1 - 0
app/build.gradle

@@ -127,6 +127,7 @@ dependencies {
     implementation project(":external:drawee")
     implementation project(":external:AndroidAutoSize")
     implementation project(":external:tcturing")
+    implementation project(":external:zxing")
 
     //test
     testImplementation libs.junit

+ 7 - 0
app/src/main/AndroidManifest.xml

@@ -43,6 +43,13 @@
             android:theme="@style/AppTheme" />
         <activity android:name=".sound.SoundActivity" />
         <activity android:name=".effect.TestAnimViewActivity" />
+        <activity android:name=".zxing.ZXingActivity" />
+        <activity android:name="com.journeyapps.barcodescanner.CaptureActivity"
+            android:clearTaskOnLaunch="true"
+            android:screenOrientation="sensorLandscape"
+            android:stateNotNeeded="true"
+            android:theme="@style/zxing_CaptureTheme"
+            android:windowSoftInputMode="stateAlwaysHidden"/>
 
     </application>
 

+ 5 - 0
app/src/main/java/com/adealink/frame/MainActivity.kt

@@ -21,6 +21,7 @@ import com.adealink.frame.svga.recyclerview.SvgaRecyclerViewActivity
 import com.adealink.frame.svga.viewpager.SvgaViewpagerActivity
 import com.adealink.frame.vap.VapActivity
 import com.adealink.frame.vap.VapEffectActivity
+import com.adealink.frame.zxing.ZXingActivity
 import com.wenext.frame.effectpreview.EffectPreviewSettingActivity
 
 class MainActivity : AppCompatActivity() {
@@ -109,6 +110,10 @@ class MainActivity : AppCompatActivity() {
             EffectPreviewSettingActivity.start(this)
         }
 
+        findViewById<View>(R.id.tv_zxing_test).setOnClickListener {
+            startActivity(Intent(this, ZXingActivity::class.java))
+        }
+
         floatKitManager.install(application, true)
         floatKitManager.onMainIconDoubleClick = {
             Toast.makeText(application, "double click", Toast.LENGTH_SHORT).show()

+ 91 - 0
app/src/main/java/com/adealink/frame/zxing/ZXingActivity.kt

@@ -0,0 +1,91 @@
+package com.adealink.frame.zxing
+
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.widget.ImageView
+import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.AppCompatEditText
+import androidx.appcompat.widget.AppCompatImageView
+import com.adealink.frame.R
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.EncodeHintType
+import com.google.zxing.client.android.Intents
+import com.journeyapps.barcodescanner.BarcodeEncoder
+import com.journeyapps.barcodescanner.ScanContract
+import com.journeyapps.barcodescanner.ScanOptions
+
+
+class ZXingActivity : AppCompatActivity() {
+
+    private val barcodeLauncher: ActivityResultLauncher<ScanOptions?> = registerForActivityResult(
+        ScanContract(),
+        { result ->
+            if (result.getContents() == null) {
+                val originalIntent: Intent? = result.getOriginalIntent()
+                if (originalIntent == null) {
+                    Log.d("ZXingActivity", "Cancelled scan")
+                    Toast.makeText(this@ZXingActivity, "Cancelled", Toast.LENGTH_LONG).show()
+                } else if (originalIntent.hasExtra(com.google.zxing.client.android.Intents.Scan.MISSING_CAMERA_PERMISSION)) {
+                    Log.d("ZXingActivity", "Cancelled scan due to missing camera permission")
+                    Toast.makeText(
+                        this@ZXingActivity,
+                        "Cancelled due to missing camera permission",
+                        Toast.LENGTH_LONG
+                    ).show()
+                }
+            } else {
+                Log.d("ZXingActivity", "Scanned")
+                Toast.makeText(
+                    this@ZXingActivity,
+                    "Scanned: " + result.getContents(),
+                    Toast.LENGTH_LONG
+                ).show()
+            }
+        })
+
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_zxing)
+
+        findViewById<View>(R.id.btn_scan_qr_code)?.setOnClickListener {
+            val options = ScanOptions()
+            options.addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN)
+            barcodeLauncher.launch(options)
+        }
+
+        val input = findViewById<AppCompatEditText>(R.id.et_input)
+        findViewById<View>(R.id.btn_generate_qr_code)?.setOnClickListener {
+            generateQRCode(input.text?.toString())
+        }
+
+    }
+
+    private fun generateQRCode(text: String?) {
+        Log.i("zxing", "generateQRCode, $text")
+        if (text.isNullOrBlank()) {
+            return
+        }
+        try {
+            val barcodeEncoder = BarcodeEncoder()
+            val bitmap = barcodeEncoder.encodeBitmap(
+                text,
+                BarcodeFormat.QR_CODE,
+                400, 400,
+                mapOf<EncodeHintType, Any>(
+                    EncodeHintType.CHARACTER_SET to "UTF-8",
+                    EncodeHintType.MARGIN to "2"
+                )
+            )
+            val imageViewQrCode = findViewById<AppCompatImageView>(R.id.iv_result)
+            imageViewQrCode.setImageBitmap(bitmap)
+        } catch (e: Exception) {
+            Log.e("zxing", "generateQRCode fail, ${e.message}", e)
+        }
+    }
+
+}

+ 11 - 0
app/src/main/res/layout/activity_main.xml

@@ -175,5 +175,16 @@
             android:textSize="16sp"
             tools:ignore="HardcodedText" />
 
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/tv_zxing_test"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            android:paddingVertical="8dp"
+            android:text="二维码测试"
+            android:textSize="16sp"
+            tools:ignore="HardcodedText" />
+
     </androidx.appcompat.widget.LinearLayoutCompat>
 </ScrollView>

+ 47 - 0
app/src/main/res/layout/activity_zxing.xml

@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_scan_qr_code"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="扫描二维码"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.appcompat.widget.AppCompatEditText
+        android:id="@+id/et_input"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:hint="输入二维码文案"
+        android:textSize="16sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/btn_scan_qr_code" />
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_generate_qr_code"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="生成二维码"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/et_input" />
+
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/iv_result"
+        android:layout_width="200dp"
+        android:layout_height="200dp"
+        android:scaleType="fitXY"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/btn_generate_qr_code" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 1 - 0
external/zxing/.gitignore

@@ -0,0 +1 @@
+/build

+ 67 - 0
external/zxing/build.gradle

@@ -0,0 +1,67 @@
+plugins {
+    id 'com.android.library'
+    id 'org.jetbrains.kotlin.android'
+    id 'maven-publish'
+}
+
+ext {
+    GROUP_ID = 'com.wenext.android'
+    ARTIFACT_ID = 'zxing'
+    VERSION = '6.1.0'
+}
+
+if (project.FRAME_DEBUG != "true") {
+    apply from: "../../publish.gradle"
+}
+
+android {
+    namespace 'com.journeyapps.barcodescanner'
+    compileSdk libs.versions.compileSdk.get().toInteger()
+
+    defaultConfig {
+        minSdk libs.versions.minSdk.get().toInteger()
+        targetSdk libs.versions.targetSdk.get().toInteger()
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles "consumer-rules.pro"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_17
+        targetCompatibility JavaVersion.VERSION_17
+    }
+    kotlinOptions {
+        jvmTarget = JavaVersion.VERSION_17.majorVersion
+    }
+}
+
+dependencies {
+    //androidx
+    implementation libs.androidx.core
+    implementation libs.androidx.appcompat
+    implementation libs.androidx.fragment.ktx
+
+    //android
+    implementation libs.android.material
+
+    //io
+    api libs.zxing
+
+    //frame
+    compileOnly project(":frame:zero")
+    compileOnly project(":frame:base")
+    compileOnly project(":frame:zero")
+    compileOnly project(":frame:coroutine")
+    compileOnly project(":frame:util")
+
+    //test
+    testImplementation libs.junit
+    androidTestImplementation libs.androidx.junit
+    androidTestImplementation libs.androidx.espresso.core
+}

+ 41 - 0
external/zxing/src/AndroidManifest.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2008 ZXing authors
+
+ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+  <uses-permission android:name="android.permission.CAMERA"/>
+
+  <!-- Don't require camera, as this requires a rear camera. This allows it to work on the Nexus 7 -->
+  <uses-feature android:name="android.hardware.camera" android:required="false"/>
+  <uses-feature android:name="android.hardware.camera.front" android:required="false"/>
+  <!-- TODO replace above two with next line after Android 4.2 -->
+  <!-- <uses-feature android:name="android.hardware.camera.any"/> -->
+  <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
+  <uses-feature android:name="android.hardware.camera.flash" android:required="false"/>
+  <uses-feature android:name="android.hardware.screen.landscape" android:required="false" />
+  <uses-feature android:name="android.hardware.wifi" android:required="false"/>
+
+
+  <application>
+      <activity android:name="com.journeyapps.barcodescanner.CaptureActivity"
+                android:clearTaskOnLaunch="true"
+                android:screenOrientation="sensorLandscape"
+                android:stateNotNeeded="true"
+                android:theme="@style/zxing_CaptureTheme"
+                android:windowSoftInputMode="stateAlwaysHidden"/>
+  </application>
+
+</manifest>

+ 93 - 0
external/zxing/src/main/java/com/google/zxing/client/android/AmbientLightManager.java

@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2012 ZXing authors
+ *
+ * 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.zxing.client.android;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+
+import com.journeyapps.barcodescanner.camera.CameraManager;
+import com.journeyapps.barcodescanner.camera.CameraSettings;
+
+/**
+ * Detects ambient light and switches on the front light when very dark, and off again when sufficiently light.
+ *
+ * @author Sean Owen
+ * @author Nikolaus Huber
+ */
+public final class AmbientLightManager implements SensorEventListener {
+
+    private static final float TOO_DARK_LUX = 45.0f;
+    private static final float BRIGHT_ENOUGH_LUX = 450.0f;
+
+    private CameraManager cameraManager;
+    private CameraSettings cameraSettings;
+    private Sensor lightSensor;
+    private Context context;
+
+    private Handler handler;
+
+    public AmbientLightManager(Context context, CameraManager cameraManager, CameraSettings settings) {
+        this.context = context;
+        this.cameraManager = cameraManager;
+        this.cameraSettings = settings;
+
+        this.handler = new Handler();
+    }
+
+    public void start() {
+        if (cameraSettings.isAutoTorchEnabled()) {
+            SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+            lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
+            if (lightSensor != null) {
+                sensorManager.registerListener(this, lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
+            }
+        }
+    }
+
+    public void stop() {
+        if (lightSensor != null) {
+            SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+            sensorManager.unregisterListener(this);
+            lightSensor = null;
+        }
+    }
+
+    private void setTorch(final boolean on) {
+        handler.post(() -> cameraManager.setTorch(on));
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent sensorEvent) {
+        float ambientLightLux = sensorEvent.values[0];
+        if (cameraManager != null) {
+            if (ambientLightLux <= TOO_DARK_LUX) {
+                setTorch(true);
+            } else if (ambientLightLux >= BRIGHT_ENOUGH_LUX) {
+                setTorch(false);
+            }
+        }
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        // do nothing
+    }
+}

+ 138 - 0
external/zxing/src/main/java/com/google/zxing/client/android/BeepManager.java

@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * 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.zxing.client.android;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.os.Build;
+import android.os.Vibrator;
+import android.util.Log;
+
+import com.journeyapps.barcodescanner.R;
+
+import java.io.IOException;
+
+/**
+ * Manages beeps and vibrations.
+ */
+public final class BeepManager {
+
+    private static final String TAG = BeepManager.class.getSimpleName();
+
+    private static final float BEEP_VOLUME = 0.10f;
+    private static final long VIBRATE_DURATION = 200L;
+
+    private final Context context;
+
+    private boolean beepEnabled = true;
+    private boolean vibrateEnabled = false;
+
+    public BeepManager(Activity activity) {
+        activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+        // We do not keep a reference to the Activity itself, to prevent leaks
+        this.context = activity.getApplicationContext();
+    }
+
+    public boolean isBeepEnabled() {
+        return beepEnabled;
+    }
+
+    /**
+     * Call updatePrefs() after setting this.
+     *
+     * If the device is in silent mode, it will not beep.
+     *
+     * @param beepEnabled true to enable beep
+     */
+    public void setBeepEnabled(boolean beepEnabled) {
+        this.beepEnabled = beepEnabled;
+    }
+
+    public boolean isVibrateEnabled() {
+        return vibrateEnabled;
+    }
+
+    /**
+     * Call updatePrefs() after setting this.
+     *
+     * @param vibrateEnabled true to enable vibrate
+     */
+    public void setVibrateEnabled(boolean vibrateEnabled) {
+        this.vibrateEnabled = vibrateEnabled;
+    }
+
+    @SuppressLint("MissingPermission")
+    public synchronized void playBeepSoundAndVibrate() {
+        if (beepEnabled) {
+            playBeepSound();
+        }
+        if (vibrateEnabled) {
+            Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+            if (vibrator != null) {
+                vibrator.vibrate(VIBRATE_DURATION);
+            }
+        }
+    }
+
+
+    public MediaPlayer playBeepSound() {
+        MediaPlayer mediaPlayer = new MediaPlayer();
+        if (Build.VERSION.SDK_INT >= 21) {
+            mediaPlayer.setAudioAttributes(new AudioAttributes.Builder().setContentType(
+                    AudioAttributes.CONTENT_TYPE_MUSIC).build());
+        } else {
+            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+        }
+
+        mediaPlayer.setOnCompletionListener(mp -> {
+            mp.stop();
+            mp.reset();
+            mp.release();
+        });
+        mediaPlayer.setOnErrorListener((mp, what, extra) -> {
+            Log.w(TAG, "Failed to beep " + what + ", " + extra);
+            // possibly media player error, so release and recreate
+            mp.stop();
+            mp.reset();
+            mp.release();
+            return true;
+        });
+        try {
+            AssetFileDescriptor file = context.getResources().openRawResourceFd(R.raw.zxing_beep);
+            try {
+                mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength());
+            } finally {
+                file.close();
+            }
+            mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);
+            mediaPlayer.prepare();
+            mediaPlayer.start();
+            return mediaPlayer;
+        } catch (IOException ioe) {
+            Log.w(TAG, ioe);
+            mediaPlayer.reset();
+            mediaPlayer.release();
+            return null;
+        }
+    }
+}

+ 99 - 0
external/zxing/src/main/java/com/google/zxing/client/android/DecodeFormatManager.java

@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * 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.zxing.client.android;
+
+import android.content.Intent;
+
+import com.google.zxing.BarcodeFormat;
+
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public final class DecodeFormatManager {
+
+    private static final Pattern COMMA_PATTERN = Pattern.compile(",");
+
+    static final Set<BarcodeFormat> PRODUCT_FORMATS;
+    static final Set<BarcodeFormat> INDUSTRIAL_FORMATS;
+    private static final Set<BarcodeFormat> ONE_D_FORMATS;
+    static final Set<BarcodeFormat> QR_CODE_FORMATS = EnumSet.of(BarcodeFormat.QR_CODE);
+    static final Set<BarcodeFormat> DATA_MATRIX_FORMATS = EnumSet.of(BarcodeFormat.DATA_MATRIX);
+    static final Set<BarcodeFormat> AZTEC_FORMATS = EnumSet.of(BarcodeFormat.AZTEC);
+    static final Set<BarcodeFormat> PDF417_FORMATS = EnumSet.of(BarcodeFormat.PDF_417);
+
+    static {
+        PRODUCT_FORMATS = EnumSet.of(BarcodeFormat.UPC_A,
+                BarcodeFormat.UPC_E,
+                BarcodeFormat.EAN_13,
+                BarcodeFormat.EAN_8,
+                BarcodeFormat.RSS_14,
+                BarcodeFormat.RSS_EXPANDED);
+        INDUSTRIAL_FORMATS = EnumSet.of(BarcodeFormat.CODE_39,
+                BarcodeFormat.CODE_93,
+                BarcodeFormat.CODE_128,
+                BarcodeFormat.ITF,
+                BarcodeFormat.CODABAR);
+        ONE_D_FORMATS = EnumSet.copyOf(PRODUCT_FORMATS);
+        ONE_D_FORMATS.addAll(INDUSTRIAL_FORMATS);
+    }
+
+    private static final Map<String, Set<BarcodeFormat>> FORMATS_FOR_MODE;
+
+    static {
+        FORMATS_FOR_MODE = new HashMap<>();
+        FORMATS_FOR_MODE.put(Intents.Scan.ONE_D_MODE, ONE_D_FORMATS);
+        FORMATS_FOR_MODE.put(Intents.Scan.PRODUCT_MODE, PRODUCT_FORMATS);
+        FORMATS_FOR_MODE.put(Intents.Scan.QR_CODE_MODE, QR_CODE_FORMATS);
+        FORMATS_FOR_MODE.put(Intents.Scan.DATA_MATRIX_MODE, DATA_MATRIX_FORMATS);
+        FORMATS_FOR_MODE.put(Intents.Scan.AZTEC_MODE, AZTEC_FORMATS);
+        FORMATS_FOR_MODE.put(Intents.Scan.PDF417_MODE, PDF417_FORMATS);
+    }
+
+    private DecodeFormatManager() {
+    }
+
+    public static Set<BarcodeFormat> parseDecodeFormats(Intent intent) {
+        Iterable<String> scanFormats = null;
+        CharSequence scanFormatsString = intent.getStringExtra(Intents.Scan.FORMATS);
+        if (scanFormatsString != null) {
+            scanFormats = Arrays.asList(COMMA_PATTERN.split(scanFormatsString));
+        }
+        return parseDecodeFormats(scanFormats, intent.getStringExtra(Intents.Scan.MODE));
+    }
+
+    private static Set<BarcodeFormat> parseDecodeFormats(Iterable<String> scanFormats, String decodeMode) {
+        if (scanFormats != null) {
+            Set<BarcodeFormat> formats = EnumSet.noneOf(BarcodeFormat.class);
+            try {
+                for (String format : scanFormats) {
+                    formats.add(BarcodeFormat.valueOf(format));
+                }
+                return formats;
+            } catch (IllegalArgumentException iae) {
+                // ignore it then
+            }
+        }
+        if (decodeMode != null) {
+            return FORMATS_FOR_MODE.get(decodeMode);
+        }
+        return null;
+    }
+}

+ 236 - 0
external/zxing/src/main/java/com/google/zxing/client/android/DecodeHintManager.java

@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2013 ZXing authors
+ *
+ * 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.zxing.client.android;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.zxing.DecodeHintType;
+
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * @author Lachezar Dobrev
+ */
+public final class DecodeHintManager {
+
+    private static final String TAG = DecodeHintManager.class.getSimpleName();
+
+    // This pattern is used in decoding integer arrays.
+    private static final Pattern COMMA = Pattern.compile(",");
+
+    private DecodeHintManager() {
+    }
+
+    /**
+     * <p>Split a query string into a list of name-value pairs.</p>
+     *
+     * <p>This is an alternative to the {@link Uri#getQueryParameterNames()} and
+     * {@link Uri#getQueryParameters(String)}, which are quirky and not suitable
+     * for exist-only Uri parameters.</p>
+     *
+     * <p>This method ignores multiple parameters with the same name and returns the
+     * first one only. This is technically incorrect, but should be acceptable due
+     * to the method of processing Hints: no multiple values for a hint.</p>
+     *
+     * @param query query to split
+     * @return name-value pairs
+     */
+    private static Map<String, String> splitQuery(String query) {
+        Map<String, String> map = new HashMap<>();
+        int pos = 0;
+        while (pos < query.length()) {
+            if (query.charAt(pos) == '&') {
+                // Skip consecutive ampersand separators.
+                pos++;
+                continue;
+            }
+            int amp = query.indexOf('&', pos);
+            int equ = query.indexOf('=', pos);
+            if (amp < 0) {
+                // This is the last element in the query, no more ampersand elements.
+                String name;
+                String text;
+                if (equ < 0) {
+                    // No equal sign
+                    name = query.substring(pos);
+                    name = name.replace('+', ' '); // Preemptively decode +
+                    name = Uri.decode(name);
+                    text = "";
+                } else {
+                    // Split name and text.
+                    name = query.substring(pos, equ);
+                    name = name.replace('+', ' '); // Preemptively decode +
+                    name = Uri.decode(name);
+                    text = query.substring(equ + 1);
+                    text = text.replace('+', ' '); // Preemptively decode +
+                    text = Uri.decode(text);
+                }
+                if (!map.containsKey(name)) {
+                    map.put(name, text);
+                }
+                break;
+            }
+            if (equ < 0 || equ > amp) {
+                // No equal sign until the &: this is a simple parameter with no value.
+                String name = query.substring(pos, amp);
+                name = name.replace('+', ' '); // Preemptively decode +
+                name = Uri.decode(name);
+                if (!map.containsKey(name)) {
+                    map.put(name, "");
+                }
+                pos = amp + 1;
+                continue;
+            }
+            String name = query.substring(pos, equ);
+            name = name.replace('+', ' '); // Preemptively decode +
+            name = Uri.decode(name);
+            String text = query.substring(equ + 1, amp);
+            text = text.replace('+', ' '); // Preemptively decode +
+            text = Uri.decode(text);
+            if (!map.containsKey(name)) {
+                map.put(name, text);
+            }
+            pos = amp + 1;
+        }
+        return map;
+    }
+
+    static Map<DecodeHintType, ?> parseDecodeHints(Uri inputUri) {
+        String query = inputUri.getEncodedQuery();
+        if (query == null || query.isEmpty()) {
+            return null;
+        }
+
+        // Extract parameters
+        Map<String, String> parameters = splitQuery(query);
+
+        Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
+
+        for (DecodeHintType hintType : DecodeHintType.values()) {
+
+            if (hintType == DecodeHintType.CHARACTER_SET ||
+                    hintType == DecodeHintType.NEED_RESULT_POINT_CALLBACK ||
+                    hintType == DecodeHintType.POSSIBLE_FORMATS) {
+                continue; // This hint is specified in another way
+            }
+
+            String parameterName = hintType.name();
+            String parameterText = parameters.get(parameterName);
+            if (parameterText == null) {
+                continue;
+            }
+            if (hintType.getValueType().equals(Object.class)) {
+                // This is an unspecified type of hint content. Use the value as is.
+                // TODO: Can we make a different assumption on this?
+                hints.put(hintType, parameterText);
+                continue;
+            }
+            if (hintType.getValueType().equals(Void.class)) {
+                // Void hints are just flags: use the constant specified by DecodeHintType
+                hints.put(hintType, Boolean.TRUE);
+                continue;
+            }
+            if (hintType.getValueType().equals(String.class)) {
+                // A string hint: use the decoded value.
+                hints.put(hintType, parameterText);
+                continue;
+            }
+            if (hintType.getValueType().equals(Boolean.class)) {
+                // A boolean hint: a few values for false, everything else is true.
+                // An empty parameter is simply a flag-style parameter, assuming true
+                if (parameterText.isEmpty()) {
+                    hints.put(hintType, Boolean.TRUE);
+                } else if ("0".equals(parameterText) ||
+                        "false".equalsIgnoreCase(parameterText) ||
+                        "no".equalsIgnoreCase(parameterText)) {
+                    hints.put(hintType, Boolean.FALSE);
+                } else {
+                    hints.put(hintType, Boolean.TRUE);
+                }
+
+                continue;
+            }
+            if (hintType.getValueType().equals(int[].class)) {
+                // An integer array. Used to specify valid lengths.
+                // Strip a trailing comma as in Java style array initialisers.
+                if (!parameterText.isEmpty() && parameterText.charAt(parameterText.length() - 1) == ',') {
+                    parameterText = parameterText.substring(0, parameterText.length() - 1);
+                }
+                String[] values = COMMA.split(parameterText);
+                int[] array = new int[values.length];
+                for (int i = 0; i < values.length; i++) {
+                    try {
+                        array[i] = Integer.parseInt(values[i]);
+                    } catch (NumberFormatException ignored) {
+                        Log.w(TAG, "Skipping array of integers hint " + hintType + " due to invalid numeric value: '" + values[i] + '\'');
+                        array = null;
+                        break;
+                    }
+                }
+                if (array != null) {
+                    hints.put(hintType, array);
+                }
+                continue;
+            }
+            Log.w(TAG, "Unsupported hint type '" + hintType + "' of type " + hintType.getValueType());
+        }
+
+        Log.i(TAG, "Hints from the URI: " + hints);
+        return hints;
+    }
+
+    public static Map<DecodeHintType, Object> parseDecodeHints(Intent intent) {
+        Bundle extras = intent.getExtras();
+        if (extras == null || extras.isEmpty()) {
+            return null;
+        }
+        Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
+
+        for (DecodeHintType hintType : DecodeHintType.values()) {
+
+            if (hintType == DecodeHintType.CHARACTER_SET ||
+                    hintType == DecodeHintType.NEED_RESULT_POINT_CALLBACK ||
+                    hintType == DecodeHintType.POSSIBLE_FORMATS) {
+                continue; // This hint is specified in another way
+            }
+
+            String hintName = hintType.name();
+            if (extras.containsKey(hintName)) {
+                if (hintType.getValueType().equals(Void.class)) {
+                    // Void hints are just flags: use the constant specified by the DecodeHintType
+                    hints.put(hintType, Boolean.TRUE);
+                } else {
+                    Object hintData = extras.get(hintName);
+                    if (hintType.getValueType().isInstance(hintData)) {
+                        hints.put(hintType, hintData);
+                    } else {
+                        Log.w(TAG, "Ignoring hint " + hintType + " because it is not assignable from " + hintData);
+                    }
+                }
+            }
+        }
+
+        Log.i(TAG, "Hints from the Intent: " + hints);
+        return hints;
+    }
+}

+ 115 - 0
external/zxing/src/main/java/com/google/zxing/client/android/InactivityTimer.java

@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * 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.zxing.client.android;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Handler;
+
+/**
+ * Finishes an context after a period of inactivity if the device is on battery power.
+ */
+public final class InactivityTimer {
+
+    private static final String TAG = InactivityTimer.class.getSimpleName();
+
+    private static final long INACTIVITY_DELAY_MS = 5 * 60 * 1000L;
+
+    private final Context context;
+    private final BroadcastReceiver powerStatusReceiver;
+    private boolean registered = false;
+    private Handler handler;
+    private Runnable callback;
+    private boolean onBattery;
+
+    public InactivityTimer(Context context, Runnable callback) {
+        this.context = context;
+        this.callback = callback;
+
+        powerStatusReceiver = new PowerStatusReceiver();
+        handler = new Handler();
+    }
+
+    /**
+     * Trigger activity, resetting the timer.
+     */
+    public void activity() {
+        cancelCallback();
+        if (onBattery) {
+            handler.postDelayed(callback, INACTIVITY_DELAY_MS);
+        }
+    }
+
+    /**
+     * Start the activity timer.
+     */
+    public void start() {
+        registerReceiver();
+        activity();
+    }
+
+    /**
+     * Cancel the activity timer.
+     */
+    public void cancel() {
+        cancelCallback();
+        unregisterReceiver();
+    }
+
+    private void unregisterReceiver() {
+        if (registered) {
+            context.unregisterReceiver(powerStatusReceiver);
+            registered = false;
+        }
+    }
+
+    private void registerReceiver() {
+        if (!registered) {
+            context.registerReceiver(powerStatusReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+            registered = true;
+        }
+    }
+
+    private void cancelCallback() {
+        handler.removeCallbacksAndMessages(null);
+    }
+
+    private void onBattery(boolean onBattery) {
+        this.onBattery = onBattery;
+
+        // To make sure we're still running
+        if (registered) {
+            // This will either cancel or reschedule, depending on the battery status.
+            activity();
+        }
+    }
+
+    private final class PowerStatusReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
+                // 0 indicates that we're on battery
+                final boolean onBatteryNow = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) <= 0;
+                // post on handler to run in main thread
+                handler.post(() -> onBattery(onBatteryNow));
+            }
+        }
+    }
+}

+ 228 - 0
external/zxing/src/main/java/com/google/zxing/client/android/Intents.java

@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2008 ZXing authors
+ *
+ * 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.zxing.client.android;
+
+/**
+ * This class provides the constants to use when sending an Intent to Barcode Scanner.
+ * These strings are effectively API and cannot be changed.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class Intents {
+    private Intents() {
+    }
+
+    public static final class Scan {
+        /**
+         * Send this intent to open the Barcodes app in scanning mode, find a barcode, and return
+         * the results.
+         */
+        public static final String ACTION = "com.google.zxing.client.android.SCAN";
+
+        /**
+         * By default, sending this will decode all barcodes that we understand. However it
+         * may be useful to limit scanning to certain formats. Use
+         * {@link android.content.Intent#putExtra(String, String)} with one of the values below.
+         *
+         * Setting this is effectively shorthand for setting explicit formats with {@link #FORMATS}.
+         * It is overridden by that setting.
+         */
+        public static final String MODE = "SCAN_MODE";
+
+        /**
+         * Decode only UPC and EAN barcodes. This is the right choice for shopping apps which get
+         * prices, reviews, etc. for products.
+         */
+        public static final String PRODUCT_MODE = "PRODUCT_MODE";
+
+        /**
+         * Decode only 1D barcodes.
+         */
+        public static final String ONE_D_MODE = "ONE_D_MODE";
+
+        /**
+         * Decode only QR codes.
+         */
+        public static final String QR_CODE_MODE = "QR_CODE_MODE";
+
+        /**
+         * Decode only Data Matrix codes.
+         */
+        public static final String DATA_MATRIX_MODE = "DATA_MATRIX_MODE";
+
+        /**
+         * Decode only Aztec.
+         */
+        public static final String AZTEC_MODE = "AZTEC_MODE";
+
+        /**
+         * Decode only PDF417.
+         */
+        public static final String PDF417_MODE = "PDF417_MODE";
+
+        /**
+         * Comma-separated list of formats to scan for. The values must match the names of
+         * {@link com.google.zxing.BarcodeFormat}s, e.g. {@link com.google.zxing.BarcodeFormat#EAN_13}.
+         * Example: "EAN_13,EAN_8,QR_CODE". This overrides {@link #MODE}.
+         */
+        public static final String FORMATS = "SCAN_FORMATS";
+
+        /**
+         * Optional parameter to specify the id of the camera from which to recognize barcodes.
+         * Overrides the default camera that would otherwise would have been selected.
+         * If provided, should be an int.
+         */
+        public static final String CAMERA_ID = "SCAN_CAMERA_ID";
+
+        /**
+         * Optional parameter to switch the torch on at camera startup.
+         * Enables the torch on camera startup
+         * If provided, should be a boolean.
+         */
+        public static final String TORCH_ENABLED = "TORCH_ENABLED";
+
+        /**
+         * @see com.google.zxing.DecodeHintType#CHARACTER_SET
+         */
+        public static final String CHARACTER_SET = "CHARACTER_SET";
+
+        /**
+         * Set to false to disable beep. Defaults to true.
+         */
+        public static final String BEEP_ENABLED = "BEEP_ENABLED";
+
+        /**
+         * Set to true to return a path to the barcode's image as it was captured. Defaults to false.
+         */
+        public static final String BARCODE_IMAGE_ENABLED = "BARCODE_IMAGE_ENABLED";
+
+        /**
+         * Set the time to finish the scan screen.
+         */
+        public static final String TIMEOUT = "TIMEOUT";
+
+        /**
+         * Set the time to finish the scan screen.
+         */
+        public static final String MISSING_CAMERA_PERMISSION = "MISSING_CAMERA_PERMISSION";
+
+        /**
+         * Set the time to finish the scan screen.
+         */
+        public static final String SHOW_MISSING_CAMERA_PERMISSION_DIALOG = "SHOW_MISSING_CAMERA_PERMISSION_DIALOG";
+
+        /**
+         * Set the time to finish the scan screen.
+         */
+        public static final String MISSING_CAMERA_PERMISSION_DIALOG_MESSAGE = "MISSING_CAMERA_PERMISSION_DIALOG_MESSAGE";
+
+        /**
+         * Whether or not the orientation should be locked when the activity is first started.
+         * Defaults to true.
+         */
+        public static final String ORIENTATION_LOCKED = "SCAN_ORIENTATION_LOCKED";
+
+        /**
+         * Prompt to show on-screen when scanning by intent. Specified as a {@link String}.
+         */
+        public static final String PROMPT_MESSAGE = "PROMPT_MESSAGE";
+
+        /**
+         * If a barcode is found, Barcodes returns {@link android.app.Activity#RESULT_OK} to
+         * {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)}
+         * of the app which requested the scan via
+         * {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}
+         * The barcodes contents can be retrieved with
+         * {@link android.content.Intent#getStringExtra(String)}.
+         * If the user presses Back, the result code will be {@link android.app.Activity#RESULT_CANCELED}.
+         */
+        public static final String RESULT = "SCAN_RESULT";
+
+        /**
+         * Call {@link android.content.Intent#getStringExtra(String)} with {@link #RESULT_FORMAT}
+         * to determine which barcode format was found.
+         * See {@link com.google.zxing.BarcodeFormat} for possible values.
+         */
+        public static final String RESULT_FORMAT = "SCAN_RESULT_FORMAT";
+
+        /**
+         * Call {@link android.content.Intent#getStringExtra(String)} with {@link #RESULT_UPC_EAN_EXTENSION}
+         * to return the content of any UPC extension barcode that was also found. Only applicable
+         * to {@link com.google.zxing.BarcodeFormat#UPC_A} and {@link com.google.zxing.BarcodeFormat#EAN_13}
+         * formats.
+         */
+        public static final String RESULT_UPC_EAN_EXTENSION = "SCAN_RESULT_UPC_EAN_EXTENSION";
+
+        /**
+         * Call {@link android.content.Intent#getByteArrayExtra(String)} with {@link #RESULT_BYTES}
+         * to get a {@code byte[]} of raw bytes in the barcode, if available.
+         */
+        public static final String RESULT_BYTES = "SCAN_RESULT_BYTES";
+
+        /**
+         * Key for the value of {@link com.google.zxing.ResultMetadataType#ORIENTATION}, if available.
+         * Call {@link android.content.Intent#getIntArrayExtra(String)} with {@link #RESULT_ORIENTATION}.
+         */
+        public static final String RESULT_ORIENTATION = "SCAN_RESULT_ORIENTATION";
+
+        /**
+         * Key for the value of {@link com.google.zxing.ResultMetadataType#ERROR_CORRECTION_LEVEL}, if available.
+         * Call {@link android.content.Intent#getStringExtra(String)} with {@link #RESULT_ERROR_CORRECTION_LEVEL}.
+         */
+        public static final String RESULT_ERROR_CORRECTION_LEVEL = "SCAN_RESULT_ERROR_CORRECTION_LEVEL";
+
+        /**
+         * Prefix for keys that map to the values of {@link com.google.zxing.ResultMetadataType#BYTE_SEGMENTS},
+         * if available. The actual values will be set under a series of keys formed by adding 0, 1, 2, ...
+         * to this prefix. So the first byte segment is under key "SCAN_RESULT_BYTE_SEGMENTS_0" for example.
+         * Call {@link android.content.Intent#getByteArrayExtra(String)} with these keys.
+         */
+        public static final String RESULT_BYTE_SEGMENTS_PREFIX = "SCAN_RESULT_BYTE_SEGMENTS_";
+
+        /**
+         * Call {@link android.content.Intent#getStringExtra(String)} with {@link #RESULT_BARCODE_IMAGE_PATH}
+         * to get a {@code String} path to a cropped and compressed png file of the barcode's image
+         * as it was displayed. Only available if
+         * {@link com.google.zxing.integration.android.IntentIntegrator#setBarcodeImageEnabled(boolean)}
+         * is called with true.
+         */
+        public static final String RESULT_BARCODE_IMAGE_PATH = "SCAN_RESULT_IMAGE_PATH";
+
+        /**
+         * Define the scan type.
+         */
+        public static final String SCAN_TYPE = "SCAN_TYPE";
+
+        /**
+         * Scan normal barcodes white on black
+         */
+        public static final int NORMAL_SCAN = 0;
+
+        /**
+         * The scan should be inverted. White becomes black, black becomes white.
+         */
+        public static final int INVERTED_SCAN = 1;
+
+        /**
+         * Scan alternating inverted and normal barcodes.
+         */
+        public static final int MIXED_SCAN = 2;
+
+        private Scan() {
+        }
+    }
+}

+ 86 - 0
external/zxing/src/main/java/com/google/zxing/client/android/camera/open/OpenCameraInterface.java

@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 ZXing authors
+ *
+ * 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.zxing.client.android.camera.open;
+
+import android.hardware.Camera;
+import android.util.Log;
+
+public final class OpenCameraInterface {
+
+    private static final String TAG = OpenCameraInterface.class.getName();
+
+    private OpenCameraInterface() {
+    }
+
+    /**
+     * For {@link #open(int)}, means no preference for which camera to open.
+     */
+    public static final int NO_REQUESTED_CAMERA = -1;
+
+    public static int getCameraId(int requestedId) {
+        int numCameras = Camera.getNumberOfCameras();
+        if (numCameras == 0) {
+            Log.w(TAG, "No cameras!");
+            return -1;
+        }
+
+        int cameraId = requestedId;
+
+        boolean explicitRequest = cameraId >= 0;
+
+        if (!explicitRequest) {
+            // Select a camera if no explicit camera requested
+            int index = 0;
+            while (index < numCameras) {
+                Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+                Camera.getCameraInfo(index, cameraInfo);
+                if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
+                    break;
+                }
+                index++;
+            }
+
+            cameraId = index;
+        }
+
+        if (cameraId < numCameras) {
+            return cameraId;
+        } else {
+            if (explicitRequest) {
+                return -1;
+            } else {
+                return 0;
+            }
+        }
+    }
+
+    /**
+     * Opens the requested camera with {@link Camera#open(int)}, if one exists.
+     *
+     * @param requestedId camera ID of the camera to use. A negative value
+     *                    or {@link #NO_REQUESTED_CAMERA} means "no preference"
+     * @return handle to {@link Camera} that was opened
+     */
+    public static Camera open(int requestedId) {
+        int cameraId = getCameraId(requestedId);
+        if (cameraId == -1) {
+            return null;
+        } else {
+            return Camera.open(cameraId);
+        }
+    }
+}

+ 432 - 0
external/zxing/src/main/java/com/google/zxing/integration/android/IntentIntegrator.java

@@ -0,0 +1,432 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * 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.zxing.integration.android;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+
+import com.google.zxing.client.android.Intents;
+import com.journeyapps.barcodescanner.CaptureActivity;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Sean Owen
+ * @author Fred Lin
+ * @author Isaac Potoczny-Jones
+ * @author Brad Drehmer
+ * @author gcstang
+ * @deprecated Use ScanOptions and ScanContract instead.
+ */
+@SuppressWarnings("unused")
+public class IntentIntegrator {
+
+    public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits
+
+    private static final String TAG = IntentIntegrator.class.getSimpleName();
+
+
+    // supported barcode formats
+
+    // Product Codes
+    public static final String UPC_A = "UPC_A";
+    public static final String UPC_E = "UPC_E";
+    public static final String EAN_8 = "EAN_8";
+    public static final String EAN_13 = "EAN_13";
+    public static final String RSS_14 = "RSS_14";
+
+    // Other 1D
+    public static final String CODE_39 = "CODE_39";
+    public static final String CODE_93 = "CODE_93";
+    public static final String CODE_128 = "CODE_128";
+    public static final String ITF = "ITF";
+
+    public static final String RSS_EXPANDED = "RSS_EXPANDED";
+
+    // 2D
+    public static final String QR_CODE = "QR_CODE";
+    public static final String DATA_MATRIX = "DATA_MATRIX";
+    public static final String PDF_417 = "PDF_417";
+
+
+    public static final Collection<String> PRODUCT_CODE_TYPES = list(UPC_A, UPC_E, EAN_8, EAN_13, RSS_14);
+    public static final Collection<String> ONE_D_CODE_TYPES =
+            list(UPC_A, UPC_E, EAN_8, EAN_13, RSS_14, CODE_39, CODE_93, CODE_128,
+                    ITF, RSS_14, RSS_EXPANDED);
+
+    public static final Collection<String> ALL_CODE_TYPES = null;
+
+    private final Activity activity;
+    private android.app.Fragment fragment;
+    private androidx.fragment.app.Fragment supportFragment;
+
+    private final Map<String, Object> moreExtras = new HashMap<>(3);
+
+    private Collection<String> desiredBarcodeFormats;
+
+    private Class<?> captureActivity;
+
+    private int requestCode = REQUEST_CODE;
+
+    protected Class<?> getDefaultCaptureActivity() {
+        return CaptureActivity.class;
+    }
+
+    public IntentIntegrator(Activity activity) {
+        this.activity = activity;
+    }
+
+    public Class<?> getCaptureActivity() {
+        if (captureActivity == null) {
+            captureActivity = getDefaultCaptureActivity();
+        }
+        return captureActivity;
+    }
+
+    /**
+     * Set the Activity class to use. It can be any activity, but should handle the intent extras
+     * as used here.
+     *
+     * @param captureActivity the class
+     */
+    public IntentIntegrator setCaptureActivity(Class<?> captureActivity) {
+        this.captureActivity = captureActivity;
+        return this;
+    }
+
+    /**
+     * Change the request code that is used for the Intent. If it is changed, it is the caller's
+     * responsibility to check the request code from the result intent.
+     *
+     * @param requestCode the new request code
+     * @return this
+     */
+    public IntentIntegrator setRequestCode(int requestCode) {
+        if (requestCode <= 0 || requestCode > 0x0000ffff) {
+            throw new IllegalArgumentException("requestCode out of range");
+        }
+        this.requestCode = requestCode;
+        return this;
+    }
+
+    /**
+     * @param fragment {@link Fragment} invoking the integration.
+     *                 {@link #startActivityForResult(Intent, int)} will be called on the {@link Fragment} instead
+     *                 of an {@link Activity}
+     */
+    public static IntentIntegrator forSupportFragment(androidx.fragment.app.Fragment fragment) {
+        IntentIntegrator integrator = new IntentIntegrator(fragment.getActivity());
+        integrator.supportFragment = fragment;
+        return integrator;
+    }
+
+    /**
+     * @param fragment {@link Fragment} invoking the integration.
+     *                 {@link #startActivityForResult(Intent, int)} will be called on the {@link Fragment} instead
+     *                 of an {@link Activity}
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public static IntentIntegrator forFragment(Fragment fragment) {
+        IntentIntegrator integrator = new IntentIntegrator(fragment.getActivity());
+        integrator.fragment = fragment;
+        return integrator;
+    }
+
+    public Map<String, ?> getMoreExtras() {
+        return moreExtras;
+    }
+
+    public final IntentIntegrator addExtra(String key, Object value) {
+        moreExtras.put(key, value);
+        return this;
+    }
+
+    /**
+     * Set a prompt to display on the capture screen, instead of using the default.
+     *
+     * @param prompt the prompt to display
+     */
+    public final IntentIntegrator setPrompt(String prompt) {
+        if (prompt != null) {
+            addExtra(Intents.Scan.PROMPT_MESSAGE, prompt);
+        }
+        return this;
+    }
+
+    /**
+     * By default, the orientation is locked. Set to false to not lock.
+     *
+     * @param locked true to lock orientation
+     */
+    public IntentIntegrator setOrientationLocked(boolean locked) {
+        addExtra(Intents.Scan.ORIENTATION_LOCKED, locked);
+        return this;
+    }
+
+    /**
+     * Use the specified camera ID.
+     *
+     * @param cameraId camera ID of the camera to use. A negative value means "no preference".
+     * @return this
+     */
+    public IntentIntegrator setCameraId(int cameraId) {
+        if (cameraId >= 0) {
+            addExtra(Intents.Scan.CAMERA_ID, cameraId);
+        }
+        return this;
+    }
+
+    /**
+     * Set to true to enable initial torch
+     *
+     * @param enabled true to enable initial torch
+     * @return this
+     */
+    public IntentIntegrator setTorchEnabled(boolean enabled) {
+        addExtra(Intents.Scan.TORCH_ENABLED, enabled);
+        return this;
+    }
+
+
+    /**
+     * Set to false to disable beep on scan.
+     *
+     * @param enabled false to disable beep
+     * @return this
+     */
+    public IntentIntegrator setBeepEnabled(boolean enabled) {
+        addExtra(Intents.Scan.BEEP_ENABLED, enabled);
+        return this;
+    }
+
+    /**
+     * Set to true to enable saving the barcode image and sending its path in the result Intent.
+     *
+     * @param enabled true to enable barcode image
+     * @return this
+     */
+    public IntentIntegrator setBarcodeImageEnabled(boolean enabled) {
+        addExtra(Intents.Scan.BARCODE_IMAGE_ENABLED, enabled);
+        return this;
+    }
+
+    /**
+     * Set the desired barcode formats to scan.
+     *
+     * @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
+     * @return this
+     */
+    public IntentIntegrator setDesiredBarcodeFormats(Collection<String> desiredBarcodeFormats) {
+        this.desiredBarcodeFormats = desiredBarcodeFormats;
+        return this;
+    }
+
+    /**
+     * Set the desired barcode formats to scan.
+     *
+     * @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
+     * @return this
+     */
+    public IntentIntegrator setDesiredBarcodeFormats(String... desiredBarcodeFormats) {
+        this.desiredBarcodeFormats = Arrays.asList(desiredBarcodeFormats);
+        return this;
+    }
+
+    /**
+     * Initiates a scan for all known barcode types with the default camera.
+     */
+    public final void initiateScan() {
+        startActivityForResult(createScanIntent(), requestCode);
+    }
+
+    /**
+     * Initiates a scan for all known barcode types with the default camera.
+     * And starts a timer to finish on timeout
+     *
+     * @return Activity.RESULT_CANCELED and true on parameter TIMEOUT.
+     */
+    public IntentIntegrator setTimeout(long timeout) {
+        addExtra(Intents.Scan.TIMEOUT, timeout);
+        return this;
+    }
+
+    /**
+     * Create an scan intent with the specified options.
+     *
+     * @return the intent
+     */
+    public Intent createScanIntent() {
+        Intent intentScan = new Intent(activity, getCaptureActivity());
+        intentScan.setAction(Intents.Scan.ACTION);
+
+        // check which types of codes to scan for
+        if (desiredBarcodeFormats != null) {
+            // set the desired barcode types
+            StringBuilder joinedByComma = new StringBuilder();
+            for (String format : desiredBarcodeFormats) {
+                if (joinedByComma.length() > 0) {
+                    joinedByComma.append(',');
+                }
+                joinedByComma.append(format);
+            }
+            intentScan.putExtra(Intents.Scan.FORMATS, joinedByComma.toString());
+        }
+
+        intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+        attachMoreExtras(intentScan);
+        return intentScan;
+    }
+
+    /**
+     * Initiates a scan, only for a certain set of barcode types, given as strings corresponding
+     * to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
+     * like {@link #PRODUCT_CODE_TYPES} for example.
+     *
+     * @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
+     */
+    public final void initiateScan(Collection<String> desiredBarcodeFormats) {
+        setDesiredBarcodeFormats(desiredBarcodeFormats);
+        initiateScan();
+    }
+
+    /**
+     * Start an activity. This method is defined to allow different methods of activity starting for
+     * newer versions of Android and for compatibility library.
+     *
+     * @param intent Intent to start.
+     * @param code   Request code for the activity
+     * @see android.app.Activity#startActivityForResult(Intent, int)
+     * @see android.app.Fragment#startActivityForResult(Intent, int)
+     */
+    protected void startActivityForResult(Intent intent, int code) {
+        if (fragment != null) {
+            fragment.startActivityForResult(intent, code);
+        } else if (supportFragment != null) {
+            supportFragment.startActivityForResult(intent, code);
+        } else {
+            activity.startActivityForResult(intent, code);
+        }
+    }
+
+    protected void startActivity(Intent intent) {
+        if (fragment != null) {
+            fragment.startActivity(intent);
+        } else if (supportFragment != null) {
+            supportFragment.startActivity(intent);
+        } else {
+            activity.startActivity(intent);
+        }
+    }
+
+    /**
+     * <p>Call this from your {@link Activity}'s
+     * {@link Activity#onActivityResult(int, int, Intent)} method.</p>
+     * <p>
+     * This checks that the requestCode is equal to the default REQUEST_CODE.
+     *
+     * @param requestCode request code from {@code onActivityResult()}
+     * @param resultCode  result code from {@code onActivityResult()}
+     * @param intent      {@link Intent} from {@code onActivityResult()}
+     * @return null if the event handled here was not related to this class, or
+     * else an {@link IntentResult} containing the result of the scan. If the user cancelled scanning,
+     * the fields will be null.
+     * @deprecated Not compatible with setRequestCode(). Use parseActivityResult(resultCode, intent) instead.
+     */
+    public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) {
+        if (requestCode == REQUEST_CODE) {
+            return parseActivityResult(resultCode, intent);
+        }
+        return null;
+    }
+
+    /**
+     * Parse activity result, without checking the request code.
+     *
+     * @param resultCode result code from {@code onActivityResult()}
+     * @param intent     {@link Intent} from {@code onActivityResult()}
+     * @return an {@link IntentResult} containing the result of the scan. If the user cancelled scanning,
+     * the fields will be null.
+     */
+    public static IntentResult parseActivityResult(int resultCode, Intent intent) {
+        if (resultCode == Activity.RESULT_OK) {
+            String contents = intent.getStringExtra(Intents.Scan.RESULT);
+            String formatName = intent.getStringExtra(Intents.Scan.RESULT_FORMAT);
+            byte[] rawBytes = intent.getByteArrayExtra(Intents.Scan.RESULT_BYTES);
+            int intentOrientation = intent.getIntExtra(Intents.Scan.RESULT_ORIENTATION, Integer.MIN_VALUE);
+            Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation;
+            String errorCorrectionLevel = intent.getStringExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL);
+            String barcodeImagePath = intent.getStringExtra(Intents.Scan.RESULT_BARCODE_IMAGE_PATH);
+            return new IntentResult(contents,
+                    formatName,
+                    rawBytes,
+                    orientation,
+                    errorCorrectionLevel,
+                    barcodeImagePath,
+                    intent);
+        }
+        return new IntentResult(intent);
+    }
+
+    private static List<String> list(String... values) {
+        return Collections.unmodifiableList(Arrays.asList(values));
+    }
+
+    private void attachMoreExtras(Intent intent) {
+        for (Map.Entry<String, Object> entry : moreExtras.entrySet()) {
+            String key = entry.getKey();
+            Object value = entry.getValue();
+            // Kind of hacky
+            if (value instanceof Integer) {
+                intent.putExtra(key, (Integer) value);
+            } else if (value instanceof Long) {
+                intent.putExtra(key, (Long) value);
+            } else if (value instanceof Boolean) {
+                intent.putExtra(key, (Boolean) value);
+            } else if (value instanceof Double) {
+                intent.putExtra(key, (Double) value);
+            } else if (value instanceof Float) {
+                intent.putExtra(key, (Float) value);
+            } else if (value instanceof Bundle) {
+                intent.putExtra(key, (Bundle) value);
+            } else if (value instanceof int[]) {
+                intent.putExtra(key, (int[]) value);
+            } else if (value instanceof long[]) {
+                intent.putExtra(key, (long[]) value);
+            } else if (value instanceof boolean[]) {
+                intent.putExtra(key, (boolean[]) value);
+            } else if (value instanceof double[]) {
+                intent.putExtra(key, (double[]) value);
+            } else if (value instanceof float[]) {
+                intent.putExtra(key, (float[]) value);
+            } else if (value instanceof String[]) {
+                intent.putExtra(key, (String[]) value);
+            } else {
+                intent.putExtra(key, value.toString());
+            }
+        }
+    }
+}

+ 120 - 0
external/zxing/src/main/java/com/google/zxing/integration/android/IntentResult.java

@@ -0,0 +1,120 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * 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.zxing.integration.android;
+
+import android.content.Intent;
+
+/**
+ * <p>Encapsulates the result of a barcode scan invoked through {@link IntentIntegrator}.</p>
+ *
+ * @author Sean Owen
+ */
+public final class IntentResult {
+
+    private final String contents;
+    private final String formatName;
+    private final byte[] rawBytes;
+    private final Integer orientation;
+    private final String errorCorrectionLevel;
+    private final String barcodeImagePath;
+    private final Intent originalIntent;
+
+    IntentResult() {
+        this(null, null, null, null, null, null, null);
+    }
+
+    IntentResult(Intent intent) {
+        this(null, null, null, null, null, null, intent);
+    }
+
+    IntentResult(String contents,
+                 String formatName,
+                 byte[] rawBytes,
+                 Integer orientation,
+                 String errorCorrectionLevel,
+                 String barcodeImagePath,
+                 Intent originalIntent) {
+        this.contents = contents;
+        this.formatName = formatName;
+        this.rawBytes = rawBytes;
+        this.orientation = orientation;
+        this.errorCorrectionLevel = errorCorrectionLevel;
+        this.barcodeImagePath = barcodeImagePath;
+        this.originalIntent = originalIntent;
+    }
+
+    /**
+     * @return raw content of barcode
+     */
+    public String getContents() {
+        return contents;
+    }
+
+    /**
+     * @return name of format, like "QR_CODE", "UPC_A". See {@code BarcodeFormat} for more format names.
+     */
+    public String getFormatName() {
+        return formatName;
+    }
+
+    /**
+     * @return raw bytes of the barcode content, if applicable, or null otherwise
+     */
+    public byte[] getRawBytes() {
+        return rawBytes;
+    }
+
+    /**
+     * @return rotation of the image, in degrees, which resulted in a successful scan. May be null.
+     */
+    public Integer getOrientation() {
+        return orientation;
+    }
+
+    /**
+     * @return name of the error correction level used in the barcode, if applicable
+     */
+    public String getErrorCorrectionLevel() {
+        return errorCorrectionLevel;
+    }
+
+    /**
+     * @return path to a temporary file containing the barcode image, if applicable, or null otherwise
+     */
+    public String getBarcodeImagePath() {
+        return barcodeImagePath;
+    }
+
+    /**
+     * @return the original intent
+     */
+    public Intent getOriginalIntent() {
+        return originalIntent;
+    }
+
+    @Override
+    public String toString() {
+        int rawBytesLength = rawBytes == null ? 0 : rawBytes.length;
+        return "Format: " + formatName + '\n' +
+            "Contents: " + contents + '\n' +
+            "Raw bytes: (" + rawBytesLength + " bytes)\n" +
+            "Orientation: " + orientation + '\n' +
+            "EC level: " + errorCorrectionLevel + '\n' +
+            "Barcode image: " + barcodeImagePath + '\n' +
+            "Original intent: " + originalIntent + '\n';
+    }
+}

+ 31 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/BarcodeCallback.java

@@ -0,0 +1,31 @@
+package com.journeyapps.barcodescanner;
+
+import com.google.zxing.ResultPoint;
+
+import java.util.List;
+
+/**
+ * Callback that is notified when a barcode is scanned.
+ */
+public interface BarcodeCallback {
+    /**
+     * Barcode was successfully scanned.
+     *
+     * @param result the result
+     */
+    void barcodeResult(BarcodeResult result);
+
+    /**
+     * ResultPoints are detected. This may be called whether or not the scanning was successful.
+     *
+     * This is mainly useful to give some feedback to the user while scanning.
+     *
+     * Do not depend on this being called at any specific point in the decode cycle.
+     *
+     * This is a default method and can be omitted by the implementing class.
+     *
+     * @param resultPoints points potentially identifying a barcode
+     */
+    default void possibleResultPoints(List<ResultPoint> resultPoints) {
+    }
+}

+ 73 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/BarcodeEncoder.java

@@ -0,0 +1,73 @@
+package com.journeyapps.barcodescanner;
+
+import android.graphics.Bitmap;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * Helper class for encoding barcodes as a Bitmap.
+ *
+ * Adapted from QRCodeEncoder, from the zxing project:
+ * https://github.com/zxing/zxing
+ *
+ * Licensed under the Apache License, Version 2.0.
+ */
+public class BarcodeEncoder {
+    private static final int WHITE = 0xFFFFFFFF;
+    private static final int BLACK = 0xFF000000;
+
+
+    public BarcodeEncoder() {
+    }
+
+    public Bitmap createBitmap(BitMatrix matrix) {
+        int width = matrix.getWidth();
+        int height = matrix.getHeight();
+        int[] pixels = new int[width * height];
+        for (int y = 0; y < height; y++) {
+            int offset = y * width;
+            for (int x = 0; x < width; x++) {
+                pixels[offset + x] = matrix.get(x, y) ? BLACK : WHITE;
+            }
+        }
+
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
+        return bitmap;
+    }
+
+    public BitMatrix encode(String contents, BarcodeFormat format, int width, int height) throws WriterException {
+        try {
+            return new MultiFormatWriter().encode(contents, format, width, height);
+        } catch (WriterException e) {
+            throw e;
+        } catch (Exception e) {
+            // ZXing sometimes throws an IllegalArgumentException
+            throw new WriterException(e);
+        }
+    }
+
+    public BitMatrix encode(String contents, BarcodeFormat format, int width, int height, Map<EncodeHintType, ?> hints) throws WriterException {
+        try {
+            return new MultiFormatWriter().encode(contents, format, width, height, hints);
+        } catch (WriterException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new WriterException(e);
+        }
+    }
+
+    public Bitmap encodeBitmap(String contents, BarcodeFormat format, int width, int height) throws WriterException {
+        return createBitmap(encode(contents, format, width, height));
+    }
+
+    public Bitmap encodeBitmap(String contents, BarcodeFormat format, int width, int height, Map<EncodeHintType, ?> hints) throws WriterException {
+        return createBitmap(encode(contents, format, width, height, hints));
+    }
+}

+ 176 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/BarcodeResult.java

@@ -0,0 +1,176 @@
+package com.journeyapps.barcodescanner;
+
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.Result;
+import com.google.zxing.ResultMetadataType;
+import com.google.zxing.ResultPoint;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This contains the result of a barcode scan.
+ *
+ * This class delegate all read-only fields of {@link com.google.zxing.Result},
+ * and adds a bitmap with scanned barcode.
+ */
+public class BarcodeResult {
+    private static final float PREVIEW_LINE_WIDTH = 4.0f;
+    private static final float PREVIEW_DOT_WIDTH = 10.0f;
+
+    protected Result mResult;
+    protected SourceData sourceData;
+
+    private final int mScaleFactor = 2;
+
+    public BarcodeResult(Result result, SourceData sourceData) {
+        this.mResult = result;
+        this.sourceData = sourceData;
+    }
+
+    private static void drawLine(Canvas canvas, Paint paint, ResultPoint a, ResultPoint b, int scaleFactor) {
+        if (a != null && b != null) {
+            canvas.drawLine(a.getX() / scaleFactor,
+                    a.getY() / scaleFactor,
+                    b.getX() / scaleFactor,
+                    b.getY() / scaleFactor,
+                    paint);
+        }
+    }
+
+    /**
+     * @return wrapped {@link com.google.zxing.Result}
+     */
+    public Result getResult() {
+        return mResult;
+    }
+
+    /**
+     * @return {@link Bitmap} with barcode preview
+     * @see #getBitmapWithResultPoints(int)
+     */
+    public Bitmap getBitmap() {
+        return sourceData.getBitmap(null, mScaleFactor);
+    }
+
+    public List<ResultPoint> getTransformedResultPoints() {
+        if (this.mResult.getResultPoints() == null) {
+            return Collections.emptyList();
+        }
+        return transformResultPoints(Arrays.asList(this.mResult.getResultPoints()), this.sourceData);
+    }
+
+    /**
+     * @param color Color of result points
+     * @return {@link Bitmap} with result points on it, or plain bitmap, if no result points
+     */
+    public Bitmap getBitmapWithResultPoints(int color) {
+        Bitmap bitmap = getBitmap();
+        Bitmap barcode = bitmap;
+        List<ResultPoint> points = getTransformedResultPoints();
+
+        if (!points.isEmpty() && bitmap != null) {
+            barcode = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(barcode);
+            canvas.drawBitmap(bitmap, 0, 0, null);
+            Paint paint = new Paint();
+            paint.setColor(color);
+            if (points.size() == 2) {
+                paint.setStrokeWidth(PREVIEW_LINE_WIDTH);
+                drawLine(canvas, paint, points.get(0), points.get(1), mScaleFactor);
+            } else if (points.size() == 4 &&
+                    (mResult.getBarcodeFormat() == BarcodeFormat.UPC_A ||
+                            mResult.getBarcodeFormat() == BarcodeFormat.EAN_13)) {
+                // Hacky special case -- draw two lines, for the barcode and metadata
+                drawLine(canvas, paint, points.get(0), points.get(1), mScaleFactor);
+                drawLine(canvas, paint, points.get(2), points.get(3), mScaleFactor);
+            } else {
+                paint.setStrokeWidth(PREVIEW_DOT_WIDTH);
+                for (ResultPoint point : points) {
+                    if (point != null) {
+                        canvas.drawPoint(point.getX() / mScaleFactor, point.getY() / mScaleFactor, paint);
+                    }
+                }
+            }
+        }
+        return barcode;
+    }
+
+    /**
+     *
+     * @return Bitmap preview scale factor
+     */
+    public int getBitmapScaleFactor(){
+        return mScaleFactor;
+    }
+
+    /**
+     * @return raw text encoded by the barcode
+     * @see Result#getText()
+     */
+    public String getText() {
+        return mResult.getText();
+    }
+
+    /**
+     * @return raw bytes encoded by the barcode, if applicable, otherwise {@code null}
+     * @see Result#getRawBytes()
+     */
+    public byte[] getRawBytes() {
+        return mResult.getRawBytes();
+    }
+
+    /**
+     * @return points related to the barcode in the image. These are typically points
+     * identifying finder patterns or the corners of the barcode. The exact meaning is
+     * specific to the type of barcode that was decoded.
+     * @see Result#getResultPoints()
+     */
+    public ResultPoint[] getResultPoints() {
+        return mResult.getResultPoints();
+    }
+
+    /**
+     * @return {@link BarcodeFormat} representing the format of the barcode that was decoded
+     * @see Result#getBarcodeFormat()
+     */
+    public BarcodeFormat getBarcodeFormat() {
+        return mResult.getBarcodeFormat();
+    }
+
+    /**
+     * @return {@link Map} mapping {@link ResultMetadataType} keys to values. May be
+     * {@code null}. This contains optional metadata about what was detected about the barcode,
+     * like orientation.
+     * @see Result#getResultMetadata()
+     */
+    public Map<ResultMetadataType, Object> getResultMetadata() {
+        return mResult.getResultMetadata();
+    }
+
+    public long getTimestamp() {
+        return mResult.getTimestamp();
+    }
+
+    @Override
+    public String toString() {
+        return mResult.getText();
+    }
+
+
+    public static List<ResultPoint> transformResultPoints(List<ResultPoint> resultPoints, SourceData sourceData) {
+        List<ResultPoint> scaledPoints = new ArrayList<>(resultPoints.size());
+        for (ResultPoint point : resultPoints) {
+            scaledPoints.add(sourceData.translateResultPoint(point));
+        }
+        return scaledPoints;
+    }
+}

+ 208 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/BarcodeView.java

@@ -0,0 +1,208 @@
+package com.journeyapps.barcodescanner;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.ResultPoint;
+import com.journeyapps.barcodescanner.R;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A view for scanning barcodes.
+ *
+ * Two methods MUST be called to manage the state:
+ * 1. resume() - initialize the camera and start the preview. Call from the Activity's onResume().
+ * 2. pause() - stop the preview and release any resources. Call from the Activity's onPause().
+ *
+ * Start decoding with decodeSingle() or decodeContinuous(). Stop decoding with stopDecoding().
+ *
+ * @see CameraPreview for more details on the preview lifecycle.
+ */
+public class BarcodeView extends CameraPreview {
+
+    private enum DecodeMode {
+        NONE,
+        SINGLE,
+        CONTINUOUS
+    }
+
+    private DecodeMode decodeMode = DecodeMode.NONE;
+    private BarcodeCallback callback = null;
+    private DecoderThread decoderThread;
+
+    private DecoderFactory decoderFactory;
+
+
+    private Handler resultHandler;
+
+    private final Handler.Callback resultCallback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message message) {
+            if (message.what == R.id.zxing_decode_succeeded) {
+                BarcodeResult result = (BarcodeResult) message.obj;
+
+                if (result != null) {
+                    if (callback != null && decodeMode != DecodeMode.NONE) {
+                        callback.barcodeResult(result);
+                        if (decodeMode == DecodeMode.SINGLE) {
+                            stopDecoding();
+                        }
+                    }
+                }
+                return true;
+            } else if (message.what == R.id.zxing_decode_failed) {
+                // Failed. Next preview is automatically tried.
+                return true;
+            } else if (message.what == R.id.zxing_possible_result_points) {
+                //noinspection unchecked
+                List<ResultPoint> resultPoints = (List<ResultPoint>) message.obj;
+                if (callback != null && decodeMode != DecodeMode.NONE) {
+                    callback.possibleResultPoints(resultPoints);
+                }
+                return true;
+            }
+            return false;
+        }
+    };
+
+
+    public BarcodeView(Context context) {
+        super(context);
+        initialize();
+    }
+
+    public BarcodeView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initialize();
+    }
+
+    public BarcodeView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initialize();
+    }
+
+    private void initialize() {
+        decoderFactory = new DefaultDecoderFactory();
+        resultHandler = new Handler(resultCallback);
+    }
+
+    /**
+     * Set the DecoderFactory to use. Use this to specify the formats to decode.
+     *
+     * Call this from UI thread only.
+     *
+     * @param decoderFactory the DecoderFactory creating Decoders.
+     * @see DefaultDecoderFactory
+     */
+    public void setDecoderFactory(DecoderFactory decoderFactory) {
+        Util.validateMainThread();
+
+        this.decoderFactory = decoderFactory;
+        if (this.decoderThread != null) {
+            this.decoderThread.setDecoder(createDecoder());
+        }
+    }
+
+    private Decoder createDecoder() {
+        if (decoderFactory == null) {
+            decoderFactory = createDefaultDecoderFactory();
+        }
+        DecoderResultPointCallback callback = new DecoderResultPointCallback();
+        Map<DecodeHintType, Object> hints = new HashMap<>();
+        hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, callback);
+        Decoder decoder = this.decoderFactory.createDecoder(hints);
+        callback.setDecoder(decoder);
+        return decoder;
+    }
+
+    /**
+     *
+     * @return the current DecoderFactory in use.
+     */
+    public DecoderFactory getDecoderFactory() {
+        return decoderFactory;
+    }
+
+    /**
+     * Decode a single barcode, then stop decoding.
+     *
+     * The callback will only be called on the UI thread.
+     *
+     * @param callback called with the barcode result, as well as possible ResultPoints
+     */
+    public void decodeSingle(BarcodeCallback callback) {
+        this.decodeMode = DecodeMode.SINGLE;
+        this.callback = callback;
+        startDecoderThread();
+    }
+
+    /**
+     * Continuously decode barcodes. The same barcode may be returned multiple times per second.
+     *
+     * The callback will only be called on the UI thread.
+     *
+     * @param callback called with the barcode result, as well as possible ResultPoints
+     */
+    public void decodeContinuous(BarcodeCallback callback) {
+        this.decodeMode = DecodeMode.CONTINUOUS;
+        this.callback = callback;
+        startDecoderThread();
+    }
+
+    /**
+     * Stop decoding, but do not stop the preview.
+     */
+    public void stopDecoding() {
+        this.decodeMode = DecodeMode.NONE;
+        this.callback = null;
+        stopDecoderThread();
+    }
+
+    protected DecoderFactory createDefaultDecoderFactory() {
+        return new DefaultDecoderFactory();
+    }
+
+    private void startDecoderThread() {
+        stopDecoderThread(); // To be safe
+
+        if (decodeMode != DecodeMode.NONE && isPreviewActive()) {
+            // We only start the thread if both:
+            // 1. decoding was requested
+            // 2. the preview is active
+            decoderThread = new DecoderThread(getCameraInstance(), createDecoder(), resultHandler);
+            decoderThread.setCropRect(getPreviewFramingRect());
+            decoderThread.start();
+        }
+    }
+
+    @Override
+    protected void previewStarted() {
+        super.previewStarted();
+
+        startDecoderThread();
+    }
+
+    private void stopDecoderThread() {
+        if (decoderThread != null) {
+            decoderThread.stop();
+            decoderThread = null;
+        }
+    }
+    /**
+     * Stops the live preview and decoding.
+     *
+     * Call from the Activity's onPause() method.
+     */
+    @Override
+    public void pause() {
+        stopDecoderThread();
+
+        super.pause();
+    }
+}

+ 885 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/CameraPreview.java

@@ -0,0 +1,885 @@
+package com.journeyapps.barcodescanner;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.TextureView;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.journeyapps.barcodescanner.R;
+import com.journeyapps.barcodescanner.camera.CameraInstance;
+import com.journeyapps.barcodescanner.camera.CameraParametersCallback;
+import com.journeyapps.barcodescanner.camera.CameraSettings;
+import com.journeyapps.barcodescanner.camera.CameraSurface;
+import com.journeyapps.barcodescanner.camera.CenterCropStrategy;
+import com.journeyapps.barcodescanner.camera.FitCenterStrategy;
+import com.journeyapps.barcodescanner.camera.DisplayConfiguration;
+import com.journeyapps.barcodescanner.camera.FitXYStrategy;
+import com.journeyapps.barcodescanner.camera.PreviewScalingStrategy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * CameraPreview is a view that handles displaying of a camera preview on a SurfaceView. It is
+ * intended to be used as a base for realtime processing of camera images, e.g. barcode decoding
+ * or OCR, although none of this happens in CameraPreview itself.
+ *
+ * The camera is managed on a separate thread, using CameraInstance.
+ *
+ * Two methods MUST be called on CameraPreview to manage its state:
+ * 1. resume() - initialize the camera and start the preview. Call from the Activity's onResume().
+ * 2. pause() - stop the preview and release any resources. Call from the Activity's onPause().
+ *
+ * Startup sequence:
+ *
+ * 1. Create SurfaceView.
+ * 2. open camera.
+ * 2. layout this container, to get size
+ * 3. set display config, according to the container size
+ * 4. configure()
+ * 5. wait for preview size to be ready
+ * 6. set surface size according to preview size
+ * 7. set surface and start preview
+ */
+public class CameraPreview extends ViewGroup {
+    public interface StateListener {
+        /**
+         * Preview and frame sizes are determined.
+         */
+        void previewSized();
+
+        /**
+         * Preview has started.
+         */
+        void previewStarted();
+
+        /**
+         * Preview has stopped.
+         */
+        void previewStopped();
+
+        /**
+         * The camera has errored, and cannot display a preview.
+         *
+         * @param error the error
+         */
+        void cameraError(Exception error);
+
+        /**
+         * The camera has been closed.
+         */
+        void cameraClosed();
+    }
+
+    private static final String TAG = CameraPreview.class.getSimpleName();
+
+    private CameraInstance cameraInstance;
+
+    private WindowManager windowManager;
+
+    private Handler stateHandler;
+
+    private boolean useTextureView = false;
+
+    private SurfaceView surfaceView;
+    private TextureView textureView;
+
+    private boolean previewActive = false;
+
+    private RotationListener rotationListener;
+    private int openedOrientation = -1;
+
+    // Delay after rotation change is detected before we reorientate ourselves.
+    // This is to avoid double-reinitialization when the Activity is destroyed and recreated.
+    private static final int ROTATION_LISTENER_DELAY_MS = 250;
+
+    private List<StateListener> stateListeners = new ArrayList<>();
+
+    private DisplayConfiguration displayConfiguration;
+    private CameraSettings cameraSettings = new CameraSettings();
+
+    // Size of this container, non-null after layout is performed
+    private Size containerSize;
+
+    // Size of the preview resolution
+    private Size previewSize;
+
+    // Rect placing the preview surface
+    private Rect surfaceRect;
+
+    // Size of the current surface. non-null if the surface is ready
+    private Size currentSurfaceSize;
+
+    // Framing rectangle relative to this view
+    private Rect framingRect = null;
+
+    // Framing rectangle relative to the preview resolution
+    private Rect previewFramingRect = null;
+
+    // Size of the framing rectangle. If null, defaults to using a margin percentage.
+    private Size framingRectSize = null;
+
+    // Fraction of the width / heigth to use as a margin. This fraction is used on each size, so
+    // must be smaller than 0.5;
+    private double marginFraction = 0.1d;
+
+    private PreviewScalingStrategy previewScalingStrategy = null;
+
+    private boolean torchOn = false;
+
+    @TargetApi(14)
+    private TextureView.SurfaceTextureListener surfaceTextureListener() {
+        // Cannot initialize automatically, since we may be API < 14
+        return new TextureView.SurfaceTextureListener() {
+            @Override
+            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+                onSurfaceTextureSizeChanged(surface, width, height);
+            }
+
+            @Override
+            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+                currentSurfaceSize = new Size(width, height);
+                startPreviewIfReady();
+            }
+
+            @Override
+            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+                return false;
+            }
+
+            @Override
+            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+
+            }
+        };
+    }
+
+    private final SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
+
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            currentSurfaceSize = null;
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            if (holder == null) {
+                Log.e(TAG, "*** WARNING *** surfaceChanged() gave us a null surface!");
+                return;
+            }
+            currentSurfaceSize = new Size(width, height);
+            startPreviewIfReady();
+        }
+    };
+
+    private final Handler.Callback stateCallback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message message) {
+            if (message.what == R.id.zxing_prewiew_size_ready) {
+                // At this point, we have the camera preview size, and should have containerSize and
+                // surfaceRect.
+                previewSized((Size) message.obj);
+                return true;
+            } else if (message.what == R.id.zxing_camera_error) {
+                Exception error = (Exception) message.obj;
+
+                if (isActive()) {
+                    // This check prevents multiple errors from begin passed through.
+                    pause();
+                    fireState.cameraError(error);
+                }
+            } else if (message.what == R.id.zxing_camera_closed) {
+                fireState.cameraClosed();
+            }
+            return false;
+        }
+    };
+
+    private RotationCallback rotationCallback = new RotationCallback() {
+        @Override
+        public void onRotationChanged(int rotation) {
+            // Make sure this is run on the main thread.
+            stateHandler.postDelayed(() -> rotationChanged(), ROTATION_LISTENER_DELAY_MS);
+        }
+    };
+
+    public CameraPreview(Context context) {
+        super(context);
+        initialize(context, null, 0, 0);
+    }
+
+    public CameraPreview(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initialize(context, attrs, 0, 0);
+    }
+
+    public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initialize(context, attrs, defStyleAttr, 0);
+    }
+
+    private void initialize(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        if (getBackground() == null) {
+            // Default to SurfaceView colour, so that there are less changes.
+            setBackgroundColor(Color.BLACK);
+        }
+
+        initializeAttributes(attrs);
+
+        windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+        stateHandler = new Handler(stateCallback);
+
+        rotationListener = new RotationListener();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        setupSurfaceView();
+    }
+
+    /**
+     * Initialize from XML attributes.
+     *
+     * @param attrs the attributes
+     */
+    protected void initializeAttributes(AttributeSet attrs) {
+        TypedArray styledAttributes = getContext().obtainStyledAttributes(attrs, R.styleable.zxing_camera_preview);
+
+        int framingRectWidth = (int) styledAttributes.getDimension(R.styleable.zxing_camera_preview_zxing_framing_rect_width, -1);
+        int framingRectHeight = (int) styledAttributes.getDimension(R.styleable.zxing_camera_preview_zxing_framing_rect_height, -1);
+
+        if (framingRectWidth > 0 && framingRectHeight > 0) {
+            this.framingRectSize = new Size(framingRectWidth, framingRectHeight);
+        }
+
+        this.useTextureView = styledAttributes.getBoolean(R.styleable.zxing_camera_preview_zxing_use_texture_view, true);
+
+        // See zxing_attrs.xml for the enum values
+        int scalingStrategyNumber = styledAttributes.getInteger(R.styleable.zxing_camera_preview_zxing_preview_scaling_strategy, -1);
+        if (scalingStrategyNumber == 1) {
+            previewScalingStrategy = new CenterCropStrategy();
+        } else if (scalingStrategyNumber == 2) {
+            previewScalingStrategy = new FitCenterStrategy();
+        } else if (scalingStrategyNumber == 3) {
+            previewScalingStrategy = new FitXYStrategy();
+        }
+
+        styledAttributes.recycle();
+    }
+
+    private void rotationChanged() {
+        // Confirm that it did actually change
+        if (isActive() && getDisplayRotation() != openedOrientation) {
+            pause();
+            resume();
+        }
+    }
+
+    private void setupSurfaceView() {
+        if (useTextureView) {
+            textureView = new TextureView(getContext());
+            textureView.setSurfaceTextureListener(surfaceTextureListener());
+            addView(textureView);
+        } else {
+            surfaceView = new SurfaceView(getContext());
+            surfaceView.getHolder().addCallback(surfaceCallback);
+            addView(surfaceView);
+        }
+    }
+
+    /**
+     * Add a listener to be notified of changes to the preview state, as well as camera errors.
+     *
+     * @param listener the listener
+     */
+    public void addStateListener(StateListener listener) {
+        stateListeners.add(listener);
+    }
+
+    private final StateListener fireState = new StateListener() {
+        @Override
+        public void previewSized() {
+            for (StateListener listener : stateListeners) {
+                listener.previewSized();
+            }
+        }
+
+        @Override
+        public void previewStarted() {
+            for (StateListener listener : stateListeners) {
+                listener.previewStarted();
+            }
+
+        }
+
+        @Override
+        public void previewStopped() {
+            for (StateListener listener : stateListeners) {
+                listener.previewStopped();
+            }
+        }
+
+        @Override
+        public void cameraError(Exception error) {
+            for (StateListener listener : stateListeners) {
+                listener.cameraError(error);
+            }
+        }
+
+        @Override
+        public void cameraClosed() {
+            for (StateListener listener : stateListeners) {
+                listener.cameraClosed();
+            }
+        }
+    };
+
+    private void calculateFrames() {
+        if (containerSize == null || previewSize == null || displayConfiguration == null) {
+            previewFramingRect = null;
+            framingRect = null;
+            surfaceRect = null;
+            throw new IllegalStateException("containerSize or previewSize is not set yet");
+        }
+
+        int previewWidth = previewSize.width;
+        int previewHeight = previewSize.height;
+
+        int width = containerSize.width;
+        int height = containerSize.height;
+
+        Rect scaledPreview = displayConfiguration.scalePreview(previewSize);
+        if (scaledPreview.width() <= 0 || scaledPreview.height() <= 0) {
+            // Something is not ready yet - we can't start the preview.
+            return;
+        }
+
+        surfaceRect = scaledPreview;
+
+        Rect container = new Rect(0, 0, width, height);
+        framingRect = calculateFramingRect(container, surfaceRect);
+        Rect frameInPreview = new Rect(framingRect);
+        frameInPreview.offset(-surfaceRect.left, -surfaceRect.top);
+
+        previewFramingRect = new Rect(frameInPreview.left * previewWidth / surfaceRect.width(),
+                frameInPreview.top * previewHeight / surfaceRect.height(),
+                frameInPreview.right * previewWidth / surfaceRect.width(),
+                frameInPreview.bottom * previewHeight / surfaceRect.height());
+
+        if (previewFramingRect == null || previewFramingRect.width() <= 0 || previewFramingRect.height() <= 0) {
+            previewFramingRect = null;
+            framingRect = null;
+            Log.w(TAG, "Preview frame is too small");
+        } else {
+            fireState.previewSized();
+        }
+    }
+
+    /**
+     * Call this on the main thread, while the preview is running.
+     *
+     * @param on true to turn on the torch
+     */
+    public void setTorch(boolean on) {
+        torchOn = on;
+        if (cameraInstance != null) {
+            cameraInstance.setTorch(on);
+        }
+    }
+
+    /**
+     * Changes the settings for Camera.
+     * Must be called after {@link #resume()}.
+     *
+     * @param callback {@link CameraParametersCallback}
+     */
+    public void changeCameraParameters(CameraParametersCallback callback) {
+        if (cameraInstance != null) {
+            cameraInstance.changeCameraParameters(callback);
+        }
+    }
+
+    private void containerSized(Size containerSize) {
+        this.containerSize = containerSize;
+        if (cameraInstance != null) {
+            if (cameraInstance.getDisplayConfiguration() == null) {
+                displayConfiguration = new DisplayConfiguration(getDisplayRotation(), containerSize);
+                displayConfiguration.setPreviewScalingStrategy(getPreviewScalingStrategy());
+                cameraInstance.setDisplayConfiguration(displayConfiguration);
+                cameraInstance.configureCamera();
+                if (torchOn) {
+                    cameraInstance.setTorch(torchOn);
+                }
+            }
+        }
+    }
+
+    /**
+     * Override the preview scaling strategy.
+     *
+     * @param previewScalingStrategy null for the default
+     */
+    public void setPreviewScalingStrategy(PreviewScalingStrategy previewScalingStrategy) {
+        this.previewScalingStrategy = previewScalingStrategy;
+    }
+
+    /**
+     * Override this to specify a different preview scaling strategy.
+     */
+    public PreviewScalingStrategy getPreviewScalingStrategy() {
+        if (previewScalingStrategy != null) {
+            return previewScalingStrategy;
+        }
+
+        // If we are using SurfaceTexture, it is safe to use centerCrop.
+        // For SurfaceView, it's better to use fitCenter, otherwise the preview may overlap to
+        // other views.
+        if (textureView != null) {
+            return new CenterCropStrategy();
+        } else {
+            return new FitCenterStrategy();
+        }
+
+    }
+
+    private void previewSized(Size size) {
+        this.previewSize = size;
+        if (containerSize != null) {
+            calculateFrames();
+            requestLayout();
+            startPreviewIfReady();
+        }
+    }
+
+    /**
+     * Calculate transformation for the TextureView.
+     *
+     * An identity matrix would cause the preview to be scaled up/down to fill the TextureView.
+     *
+     * @param textureSize the size of the textureView
+     * @param previewSize the camera preview resolution
+     * @return the transform matrix for the TextureView
+     */
+    protected Matrix calculateTextureTransform(Size textureSize, Size previewSize) {
+        float ratioTexture = (float) textureSize.width / (float) textureSize.height;
+        float ratioPreview = (float) previewSize.width / (float) previewSize.height;
+
+        float scaleX;
+        float scaleY;
+
+        // We scale so that either width or height fits exactly in the TextureView, and the other
+        // is bigger (cropped).
+        if (ratioTexture < ratioPreview) {
+            scaleX = ratioPreview / ratioTexture;
+            scaleY = 1;
+        } else {
+            scaleX = 1;
+            scaleY = ratioTexture / ratioPreview;
+        }
+
+        Matrix matrix = new Matrix();
+
+        matrix.setScale(scaleX, scaleY);
+
+        // Center the preview
+        float scaledWidth = textureSize.width * scaleX;
+        float scaledHeight = textureSize.height * scaleY;
+        float dx = (textureSize.width - scaledWidth) / 2;
+        float dy = (textureSize.height - scaledHeight) / 2;
+
+        // Perform the translation on the scaled preview
+        matrix.postTranslate(dx, dy);
+
+        return matrix;
+    }
+
+    private void startPreviewIfReady() {
+        if (currentSurfaceSize != null && previewSize != null && surfaceRect != null) {
+            if (surfaceView != null && currentSurfaceSize.equals(new Size(surfaceRect.width(), surfaceRect.height()))) {
+                startCameraPreview(new CameraSurface(surfaceView.getHolder()));
+            } else if (textureView != null && textureView.getSurfaceTexture() != null) {
+                if (previewSize != null) {
+                    Matrix transform = calculateTextureTransform(new Size(textureView.getWidth(), textureView.getHeight()), previewSize);
+                    textureView.setTransform(transform);
+                }
+
+                startCameraPreview(new CameraSurface(textureView.getSurfaceTexture()));
+            } else {
+                // Surface is not the correct size yet
+            }
+        }
+    }
+
+    @SuppressLint("DrawAllocation")
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        containerSized(new Size(r - l, b - t));
+
+        if (surfaceView != null) {
+            if (surfaceRect == null) {
+                // Match the container, to reduce the risk of issues. The preview should never be drawn
+                // while the surface has this size.
+                surfaceView.layout(0, 0, getWidth(), getHeight());
+            } else {
+                surfaceView.layout(surfaceRect.left, surfaceRect.top, surfaceRect.right, surfaceRect.bottom);
+            }
+        } else if (textureView != null) {
+            textureView.layout(0, 0, getWidth(), getHeight());
+        }
+    }
+
+    /**
+     * The framing rectangle, relative to this view. Use to draw the rectangle.
+     *
+     * Will never be null while the preview is active.
+     *
+     * @return the framing rect, or null
+     * @see #isPreviewActive()
+     */
+    public Rect getFramingRect() {
+        return framingRect;
+    }
+
+    /**
+     * The framing rect, relative to the camera preview resolution.
+     *
+     * Will never be null while the preview is active.
+     *
+     * @return the preview rect, or null
+     * @see #isPreviewActive()
+     */
+    public Rect getPreviewFramingRect() {
+        return previewFramingRect;
+    }
+
+    public Size getPreviewSize() {
+        return previewSize;
+    }
+
+    /**
+     * @return the CameraSettings currently in use
+     */
+    public CameraSettings getCameraSettings() {
+        return cameraSettings;
+    }
+
+    /**
+     * Set the CameraSettings. Use this to select a different camera, change exposure and torch
+     * settings, and some other options.
+     *
+     * This has no effect if the camera is already open.
+     *
+     * @param cameraSettings the new settings
+     */
+    public void setCameraSettings(CameraSettings cameraSettings) {
+        this.cameraSettings = cameraSettings;
+    }
+
+    /**
+     * Start the camera preview and decoding. Typically this should be called from the Activity's
+     * onResume() method.
+     *
+     * Call from UI thread only.
+     */
+    public void resume() {
+        // This must be safe to call multiple times
+        Util.validateMainThread();
+        Log.d(TAG, "resume()");
+
+        // initCamera() does nothing if called twice, but does log a warning
+        initCamera();
+
+        if (currentSurfaceSize != null) {
+            // The activity was paused but not stopped, so the surface still exists. Therefore
+            // surfaceCreated() won't be called, so init the camera here.
+            startPreviewIfReady();
+        } else if (surfaceView != null) {
+            // Install the callback and wait for surfaceCreated() to init the camera.
+            surfaceView.getHolder().addCallback(surfaceCallback);
+        } else if (textureView != null) {
+            if (textureView.isAvailable()) {
+                surfaceTextureListener().onSurfaceTextureAvailable(textureView.getSurfaceTexture(), textureView.getWidth(), textureView.getHeight());
+            } else {
+                textureView.setSurfaceTextureListener(surfaceTextureListener());
+            }
+        }
+
+        // To trigger surfaceSized again
+        requestLayout();
+        rotationListener.listen(getContext(), rotationCallback);
+    }
+
+    /**
+     * Pause scanning and the camera preview. Typically this should be called from the Activity's
+     * onPause() method.
+     *
+     * Call from UI thread only.
+     */
+    public void pause() {
+        // This must be safe to call multiple times.
+        Util.validateMainThread();
+        Log.d(TAG, "pause()");
+
+        openedOrientation = -1;
+        if (cameraInstance != null) {
+            cameraInstance.close();
+            cameraInstance = null;
+            previewActive = false;
+        } else {
+            stateHandler.sendEmptyMessage(R.id.zxing_camera_closed);
+        }
+        if (currentSurfaceSize == null && surfaceView != null) {
+            SurfaceHolder surfaceHolder = surfaceView.getHolder();
+            surfaceHolder.removeCallback(surfaceCallback);
+        }
+        if (currentSurfaceSize == null && textureView != null) {
+            textureView.setSurfaceTextureListener(null);
+        }
+
+        this.containerSize = null;
+        this.previewSize = null;
+        this.previewFramingRect = null;
+        rotationListener.stop();
+
+        fireState.previewStopped();
+    }
+
+    /**
+     * Pause scanning and preview; waiting for the Camera to be closed.
+     *
+     * This blocks the main thread.
+     */
+    public void pauseAndWait() {
+        CameraInstance instance = getCameraInstance();
+        pause();
+        long startTime = System.nanoTime();
+        while(instance != null && !instance.isCameraClosed()) {
+            if (System.nanoTime() - startTime > 2000000000) {
+                // Don't wait for longer than 2 seconds
+                break;
+            }
+            try {
+                Thread.sleep(1);
+            } catch (InterruptedException e) {
+                break;
+            }
+        }
+    }
+
+    public Size getFramingRectSize() {
+        return framingRectSize;
+    }
+
+    /**
+     * Set an exact size for the framing rectangle. It will be centered in the view.
+     *
+     * @param framingRectSize the size
+     */
+    public void setFramingRectSize(Size framingRectSize) {
+        this.framingRectSize = framingRectSize;
+    }
+
+    public double getMarginFraction() {
+        return marginFraction;
+    }
+
+    /**
+     * The the fraction of the width/height of view to be used as a margin for the framing rect.
+     * This is ignored if framingRectSize is specified.
+     *
+     * @param marginFraction the fraction
+     */
+    public void setMarginFraction(double marginFraction) {
+        if (marginFraction >= 0.5d) {
+            throw new IllegalArgumentException("The margin fraction must be less than 0.5");
+        }
+        this.marginFraction = marginFraction;
+    }
+
+    public boolean isUseTextureView() {
+        return useTextureView;
+    }
+
+    /**
+     * Set to true to use TextureView instead of SurfaceView.
+     *
+     * Will only have an effect on API >= 14.
+     *
+     * @param useTextureView true to use TextureView.
+     */
+    public void setUseTextureView(boolean useTextureView) {
+        this.useTextureView = useTextureView;
+    }
+
+    /**
+     * Considered active if between resume() and pause().
+     *
+     * @return true if active
+     */
+    protected boolean isActive() {
+        return cameraInstance != null;
+    }
+
+    private int getDisplayRotation() {
+        return windowManager.getDefaultDisplay().getRotation();
+    }
+
+    private void initCamera() {
+        if (cameraInstance != null) {
+            Log.w(TAG, "initCamera called twice");
+            return;
+        }
+
+        cameraInstance = createCameraInstance();
+
+        cameraInstance.setReadyHandler(stateHandler);
+        cameraInstance.open();
+
+        // Keep track of the orientation we opened at, so that we don't reopen the camera if we
+        // don't need to.
+        openedOrientation = getDisplayRotation();
+    }
+
+    /**
+     * Create a new CameraInstance.
+     *
+     * Override to use a custom CameraInstance.
+     *
+     * @return a new CameraInstance
+     */
+    protected CameraInstance createCameraInstance() {
+        CameraInstance cameraInstance = new CameraInstance(getContext());
+        cameraInstance.setCameraSettings(cameraSettings);
+        return cameraInstance;
+    }
+
+    private void startCameraPreview(CameraSurface surface) {
+        if (!previewActive && cameraInstance != null) {
+            Log.i(TAG, "Starting preview");
+            cameraInstance.setSurface(surface);
+            cameraInstance.startPreview();
+            previewActive = true;
+
+            previewStarted();
+            fireState.previewStarted();
+        }
+    }
+
+    /**
+     * Called when the preview is started. Override this to start decoding work.
+     */
+    protected void previewStarted() {
+
+    }
+
+    /**
+     * Get the current CameraInstance. This may be null, and may change when
+     * pausing / resuming the preview.
+     *
+     * While the preview is active, getCameraInstance() will never be null.
+     *
+     * @return the current CameraInstance
+     * @see #isPreviewActive()
+     */
+    public CameraInstance getCameraInstance() {
+        return cameraInstance;
+    }
+
+    /**
+     * The preview typically starts being active a while after calling resume(), and stops
+     * when calling pause().
+     *
+     * @return true if the preview is active
+     */
+    public boolean isPreviewActive() {
+        return previewActive;
+    }
+
+    /**
+     * Calculate framing rectangle, relative to the preview frame.
+     *
+     * Note that the SurfaceView may be larger than the container.
+     *
+     * Override this for more control over the framing rect calculations.
+     *
+     * @param container this container, with left = top = 0
+     * @param surface   the SurfaceView, relative to this container
+     * @return the framing rect, relative to this container
+     */
+    protected Rect calculateFramingRect(Rect container, Rect surface) {
+        // intersection is the part of the container that is used for the preview
+        Rect intersection = new Rect(container);
+        boolean intersects = intersection.intersect(surface);
+
+        if (framingRectSize != null) {
+            // Specific size is specified. Make sure it's not larger than the container or surface.
+            int horizontalMargin = Math.max(0, (intersection.width() - framingRectSize.width) / 2);
+            int verticalMargin = Math.max(0, (intersection.height() - framingRectSize.height) / 2);
+            intersection.inset(horizontalMargin, verticalMargin);
+            return intersection;
+        }
+        // margin as 10% (default) of the smaller of width, height
+        int margin = (int)Math.min(intersection.width() * marginFraction, intersection.height() * marginFraction);
+        intersection.inset(margin, margin);
+        if (intersection.height() > intersection.width()) {
+            // We don't want a frame that is taller than wide.
+            intersection.inset(0, (intersection.height() - intersection.width()) / 2);
+        }
+        return intersection;
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+
+        Bundle myState = new Bundle();
+        myState.putParcelable("super", superState);
+        myState.putBoolean("torch", torchOn);
+        return myState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (!(state instanceof Bundle)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+        Bundle myState = (Bundle)state;
+        Parcelable superState = myState.getParcelable("super");
+        super.onRestoreInstanceState(superState);
+        boolean torch = myState.getBoolean("torch");
+        setTorch(torch);
+    }
+
+    /**
+     *
+     * @return true if the camera has been closed in a background thread.
+     */
+    public boolean isCameraClosed() {
+        return cameraInstance == null || cameraInstance.isCameraClosed();
+    }
+}

+ 72 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/CaptureActivity.java

@@ -0,0 +1,72 @@
+package com.journeyapps.barcodescanner;
+
+import android.app.Activity;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import android.view.KeyEvent;
+
+import com.journeyapps.barcodescanner.R;
+
+
+/**
+ *
+ */
+public class CaptureActivity extends Activity {
+    private CaptureManager capture;
+    private DecoratedBarcodeView barcodeScannerView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        barcodeScannerView = initializeContent();
+
+        capture = new CaptureManager(this, barcodeScannerView);
+        capture.initializeFromIntent(getIntent(), savedInstanceState);
+        capture.decode();
+    }
+
+    /**
+     * Override to use a different layout.
+     *
+     * @return the DecoratedBarcodeView
+     */
+    protected DecoratedBarcodeView initializeContent() {
+        setContentView(R.layout.zxing_capture);
+        return (DecoratedBarcodeView)findViewById(R.id.zxing_barcode_scanner);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        capture.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        capture.onPause();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        capture.onDestroy();
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        capture.onSaveInstanceState(outState);
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
+        capture.onRequestPermissionsResult(requestCode, permissions, grantResults);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
+    }
+}

+ 455 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/CaptureManager.java

@@ -0,0 +1,455 @@
+package com.journeyapps.barcodescanner;
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Display;
+import android.view.Surface;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.google.zxing.ResultMetadataType;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.client.android.BeepManager;
+import com.google.zxing.client.android.InactivityTimer;
+import com.google.zxing.client.android.Intents;
+import com.journeyapps.barcodescanner.R;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Manages barcode scanning for a CaptureActivity. This class may be used to have a custom Activity
+ * (e.g. with a customized look and feel, or a different superclass), but not the barcode scanning
+ * process itself.
+ *
+ * This is intended for an Activity that is dedicated to capturing a single barcode and returning
+ * it via setResult(). For other use cases, use DefaultBarcodeScannerView or BarcodeView directly.
+ *
+ * The following is managed by this class:
+ * - Orientation lock
+ * - InactivityTimer
+ * - BeepManager
+ * - Initializing from an Intent (via IntentIntegrator)
+ * - Setting the result and finishing the Activity when a barcode is scanned
+ * - Displaying camera errors
+ */
+public class CaptureManager {
+    private static final String TAG = CaptureManager.class.getSimpleName();
+
+    private static int cameraPermissionReqCode = 250;
+
+    private Activity activity;
+    private DecoratedBarcodeView barcodeView;
+    private int orientationLock = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+    private static final String SAVED_ORIENTATION_LOCK = "SAVED_ORIENTATION_LOCK";
+    private boolean returnBarcodeImagePath = false;
+
+    private boolean showDialogIfMissingCameraPermission = true;
+    private String missingCameraPermissionDialogMessage = "";
+
+    private boolean destroyed = false;
+
+    private InactivityTimer inactivityTimer;
+    private BeepManager beepManager;
+
+    private Handler handler;
+
+    private boolean finishWhenClosed = false;
+
+    private BarcodeCallback callback = new BarcodeCallback() {
+        @Override
+        public void barcodeResult(final BarcodeResult result) {
+            barcodeView.pause();
+            beepManager.playBeepSoundAndVibrate();
+
+            handler.post(() -> returnResult(result));
+        }
+
+        @Override
+        public void possibleResultPoints(List<ResultPoint> resultPoints) {
+
+        }
+    };
+
+    private final CameraPreview.StateListener stateListener = new CameraPreview.StateListener() {
+        @Override
+        public void previewSized() {
+
+        }
+
+        @Override
+        public void previewStarted() {
+
+        }
+
+        @Override
+        public void previewStopped() {
+
+        }
+
+        @Override
+        public void cameraError(Exception error) {
+            displayFrameworkBugMessageAndExit(
+                    activity.getString(R.string.zxing_msg_camera_framework_bug)
+            );
+        }
+
+        @Override
+        public void cameraClosed() {
+            if (finishWhenClosed) {
+                Log.d(TAG, "Camera closed; finishing activity");
+                finish();
+            }
+        }
+    };
+
+    public CaptureManager(Activity activity, DecoratedBarcodeView barcodeView) {
+        this.activity = activity;
+        this.barcodeView = barcodeView;
+        barcodeView.getBarcodeView().addStateListener(stateListener);
+
+        handler = new Handler();
+
+        inactivityTimer = new InactivityTimer(activity, () -> {
+            Log.d(TAG, "Finishing due to inactivity");
+            finish();
+        });
+
+        beepManager = new BeepManager(activity);
+    }
+
+    /**
+     * Perform initialization, according to preferences set in the intent.
+     *
+     * @param intent the intent containing the scanning preferences
+     * @param savedInstanceState saved state, containing orientation lock
+     */
+    public void initializeFromIntent(Intent intent, Bundle savedInstanceState) {
+        Window window = activity.getWindow();
+        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+        if (savedInstanceState != null) {
+            // If the screen was locked and unlocked again, we may start in a different orientation
+            // (even one not allowed by the manifest). In this case we restore the orientation we were
+            // previously locked to.
+            this.orientationLock = savedInstanceState.getInt(SAVED_ORIENTATION_LOCK, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+        }
+
+        if (intent != null) {
+            // Only lock the orientation if it's not locked to something else yet
+            boolean orientationLocked = intent.getBooleanExtra(Intents.Scan.ORIENTATION_LOCKED, true);
+            if (orientationLocked) {
+                lockOrientation();
+            }
+
+            if (Intents.Scan.ACTION.equals(intent.getAction())) {
+                barcodeView.initializeFromIntent(intent);
+            }
+
+            if (!intent.getBooleanExtra(Intents.Scan.BEEP_ENABLED, true)) {
+                beepManager.setBeepEnabled(false);
+            }
+
+            if (intent.hasExtra(Intents.Scan.SHOW_MISSING_CAMERA_PERMISSION_DIALOG)) {
+                setShowMissingCameraPermissionDialog(
+                        intent.getBooleanExtra(Intents.Scan.SHOW_MISSING_CAMERA_PERMISSION_DIALOG, true),
+                        intent.getStringExtra(Intents.Scan.MISSING_CAMERA_PERMISSION_DIALOG_MESSAGE)
+                );
+            }
+
+            if (intent.hasExtra(Intents.Scan.TIMEOUT)) {
+                handler.postDelayed(this::returnResultTimeout, intent.getLongExtra(Intents.Scan.TIMEOUT, 0L));
+            }
+
+            if (intent.getBooleanExtra(Intents.Scan.BARCODE_IMAGE_ENABLED, false)) {
+                returnBarcodeImagePath = true;
+            }
+        }
+    }
+
+    /**
+     * Lock display to current orientation.
+     */
+    protected void lockOrientation() {
+        // Only get the orientation if it's not locked to one yet.
+        if (this.orientationLock == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+            // Adapted from http://stackoverflow.com/a/14565436
+            Display display = activity.getWindowManager().getDefaultDisplay();
+            int rotation = display.getRotation();
+            int baseOrientation = activity.getResources().getConfiguration().orientation;
+            int orientation = 0;
+            if (baseOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+                if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) {
+                    orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+                } else {
+                    orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+                }
+            } else if (baseOrientation == Configuration.ORIENTATION_PORTRAIT) {
+                if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_270) {
+                    orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+                } else {
+                    orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+                }
+            }
+
+            this.orientationLock = orientation;
+        }
+        //noinspection ResourceType
+        activity.setRequestedOrientation(this.orientationLock);
+    }
+
+    /**
+     * Start decoding.
+     */
+    public void decode() {
+        barcodeView.decodeSingle(callback);
+    }
+
+    /**
+     * Call from Activity#onResume().
+     */
+    public void onResume() {
+        if (Build.VERSION.SDK_INT >= 23) {
+            openCameraWithPermission();
+        } else {
+            barcodeView.resume();
+        }
+        inactivityTimer.start();
+    }
+
+    private boolean askedPermission = false;
+
+    @TargetApi(23)
+    private void openCameraWithPermission() {
+        if (ContextCompat.checkSelfPermission(this.activity, Manifest.permission.CAMERA)
+                == PackageManager.PERMISSION_GRANTED) {
+            barcodeView.resume();
+        } else if (!askedPermission) {
+            ActivityCompat.requestPermissions(this.activity,
+                    new String[]{Manifest.permission.CAMERA},
+                    cameraPermissionReqCode);
+            askedPermission = true;
+        } // else wait for permission result
+    }
+
+    /**
+     * Call from Activity#onRequestPermissionsResult
+     * @param requestCode The request code passed in {@link androidx.core.app.ActivityCompat#requestPermissions(Activity, String[], int)}.
+     * @param permissions The requested permissions.
+     * @param grantResults The grant results for the corresponding permissions
+     *     which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED}
+     *     or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
+     */
+    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
+        if (requestCode == cameraPermissionReqCode) {
+            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                // permission was granted
+                barcodeView.resume();
+            } else {
+                setMissingCameraPermissionResult();
+
+                if (showDialogIfMissingCameraPermission) {
+                    displayFrameworkBugMessageAndExit(missingCameraPermissionDialogMessage);
+                } else {
+                    closeAndFinish();
+                }
+            }
+        }
+    }
+
+    /**
+     * Call from Activity#onPause().
+     */
+    public void onPause() {
+
+        inactivityTimer.cancel();
+        barcodeView.pauseAndWait();
+    }
+
+    /**
+     * Call from Activity#onDestroy().
+     */
+    public void onDestroy() {
+        destroyed = true;
+        inactivityTimer.cancel();
+        handler.removeCallbacksAndMessages(null);
+    }
+
+    /**
+     * Call from Activity#onSaveInstanceState().
+     */
+    public void onSaveInstanceState(Bundle outState) {
+        outState.putInt(SAVED_ORIENTATION_LOCK, this.orientationLock);
+    }
+
+    /**
+     * Create a intent to return as the Activity result.
+     *
+     * @param rawResult the BarcodeResult, must not be null.
+     * @param barcodeImagePath a path to an exported file of the Barcode Image, can be null.
+     * @return the Intent
+     */
+    public static Intent resultIntent(BarcodeResult rawResult, String barcodeImagePath) {
+        Intent intent = new Intent(Intents.Scan.ACTION);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+        intent.putExtra(Intents.Scan.RESULT, rawResult.toString());
+        intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString());
+        byte[] rawBytes = rawResult.getRawBytes();
+        if (rawBytes != null && rawBytes.length > 0) {
+            intent.putExtra(Intents.Scan.RESULT_BYTES, rawBytes);
+        }
+        Map<ResultMetadataType, ?> metadata = rawResult.getResultMetadata();
+        if (metadata != null) {
+            if (metadata.containsKey(ResultMetadataType.UPC_EAN_EXTENSION)) {
+                intent.putExtra(Intents.Scan.RESULT_UPC_EAN_EXTENSION,
+                        metadata.get(ResultMetadataType.UPC_EAN_EXTENSION).toString());
+            }
+            Number orientation = (Number) metadata.get(ResultMetadataType.ORIENTATION);
+            if (orientation != null) {
+                intent.putExtra(Intents.Scan.RESULT_ORIENTATION, orientation.intValue());
+            }
+            String ecLevel = (String) metadata.get(ResultMetadataType.ERROR_CORRECTION_LEVEL);
+            if (ecLevel != null) {
+                intent.putExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL, ecLevel);
+            }
+            @SuppressWarnings("unchecked")
+            Iterable<byte[]> byteSegments = (Iterable<byte[]>) metadata.get(ResultMetadataType.BYTE_SEGMENTS);
+            if (byteSegments != null) {
+                int i = 0;
+                for (byte[] byteSegment : byteSegments) {
+                    intent.putExtra(Intents.Scan.RESULT_BYTE_SEGMENTS_PREFIX + i, byteSegment);
+                    i++;
+                }
+            }
+        }
+        if (barcodeImagePath != null) {
+            intent.putExtra(Intents.Scan.RESULT_BARCODE_IMAGE_PATH, barcodeImagePath);
+        }
+        return intent;
+    }
+
+    /**
+     * Save the barcode image to a temporary file stored in the application's cache, and return its path.
+     * Only does so if returnBarcodeImagePath is enabled.
+     *
+     * @param rawResult the BarcodeResult, must not be null
+     * @return the path or null
+     */
+    private String getBarcodeImagePath(BarcodeResult rawResult) {
+        String barcodeImagePath = null;
+        if (returnBarcodeImagePath) {
+            Bitmap bmp = rawResult.getBitmap();
+            try {
+                File bitmapFile = File.createTempFile("barcodeimage", ".jpg", activity.getCacheDir());
+                FileOutputStream outputStream = new FileOutputStream(bitmapFile);
+                bmp.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
+                outputStream.close();
+                barcodeImagePath = bitmapFile.getAbsolutePath();
+            } catch (IOException e) {
+                Log.w(TAG, "Unable to create temporary file and store bitmap! " + e);
+            }
+        }
+        return barcodeImagePath;
+    }
+
+    private void finish() {
+        activity.finish();
+    }
+
+    protected void closeAndFinish() {
+        if (barcodeView.getBarcodeView().isCameraClosed()) {
+            finish();
+        } else {
+            finishWhenClosed = true;
+        }
+
+        barcodeView.pause();
+        inactivityTimer.cancel();
+    }
+
+    private void setMissingCameraPermissionResult() {
+        Intent intent = new Intent(Intents.Scan.ACTION);
+        intent.putExtra(Intents.Scan.MISSING_CAMERA_PERMISSION, true);
+        activity.setResult(Activity.RESULT_CANCELED, intent);
+    }
+
+    protected void returnResultTimeout() {
+        Intent intent = new Intent(Intents.Scan.ACTION);
+        intent.putExtra(Intents.Scan.TIMEOUT, true);
+        activity.setResult(Activity.RESULT_CANCELED, intent);
+        closeAndFinish();
+    }
+
+    protected void returnResult(BarcodeResult rawResult) {
+        Intent intent = resultIntent(rawResult, getBarcodeImagePath(rawResult));
+        activity.setResult(Activity.RESULT_OK, intent);
+        closeAndFinish();
+    }
+
+    protected void displayFrameworkBugMessageAndExit(String message) {
+        if (activity.isFinishing() || this.destroyed || finishWhenClosed) {
+            return;
+        }
+
+        if (message.isEmpty()) {
+            message = activity.getString(R.string.zxing_msg_camera_framework_bug);
+        }
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setTitle(activity.getString(R.string.zxing_app_name));
+        builder.setMessage(message);
+        builder.setPositiveButton(R.string.zxing_button_ok, (dialog, which) -> finish());
+        builder.setOnCancelListener(dialog -> finish());
+        builder.show();
+    }
+
+    public static int getCameraPermissionReqCode() {
+        return cameraPermissionReqCode;
+    }
+
+    public static void setCameraPermissionReqCode(int cameraPermissionReqCode) {
+        CaptureManager.cameraPermissionReqCode = cameraPermissionReqCode;
+    }
+
+    /**
+     * If set to true, shows the default error dialog if camera permission is missing.
+     * <p>
+     * If set to false, instead the capture manager just finishes.
+     * <p>
+     * In both cases, the activity result is set to {@link Intents.Scan#MISSING_CAMERA_PERMISSION}
+     * and cancelled
+     */
+    public void setShowMissingCameraPermissionDialog(boolean visible) {
+        setShowMissingCameraPermissionDialog(visible, "");
+    }
+
+    /**
+     * If set to true, shows the specified error dialog message if camera permission is missing.
+     * <p>
+     * If set to false, instead the capture manager just finishes.
+     * <p>
+     * In both cases, the activity result is set to {@link Intents.Scan#MISSING_CAMERA_PERMISSION}
+     * and cancelled
+     */
+    public void setShowMissingCameraPermissionDialog(boolean visible, String message) {
+        showDialogIfMissingCameraPermission = visible;
+        missingCameraPermissionDialogMessage = message != null ? message : "";
+    }
+}

+ 21 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/CompoundBarcodeView.java

@@ -0,0 +1,21 @@
+package com.journeyapps.barcodescanner;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * Used as an alias for DecoratedBarcodeView, for backwards-compatibility.
+ */
+public class CompoundBarcodeView extends DecoratedBarcodeView {
+    public CompoundBarcodeView(Context context) {
+        super(context);
+    }
+
+    public CompoundBarcodeView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CompoundBarcodeView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+}

+ 104 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/Decoder.java

@@ -0,0 +1,104 @@
+package com.journeyapps.barcodescanner;
+
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.Reader;
+import com.google.zxing.Result;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.HybridBinarizer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class for decoding images.
+ *
+ * A decoder contains all the configuration required for the binarization and decoding process.
+ *
+ * The actual decoding should happen on a dedicated thread.
+ */
+public class Decoder implements ResultPointCallback {
+    private Reader reader;
+
+    /**
+     * Create a new Decoder with the specified Reader.
+     *
+     * It is recommended to use an instance of MultiFormatReader in most cases.
+     *
+     * @param reader the reader
+     */
+    public Decoder(Reader reader) {
+        this.reader = reader;
+    }
+
+    protected Reader getReader() {
+        return reader;
+    }
+
+    /**
+     * Given an image source, attempt to decode the barcode.
+     *
+     * Must not raise an exception.
+     *
+     * @param source the image source
+     * @return a Result or null
+     */
+    public Result decode(LuminanceSource source) {
+        return decode(toBitmap(source));
+    }
+
+    /**
+     * Given an image source, convert to a binary bitmap.
+     *
+     * Override this to use a custom binarizer.
+     *
+     * @param source the image source
+     * @return a BinaryBitmap
+     */
+    protected BinaryBitmap toBitmap(LuminanceSource source) {
+        return new BinaryBitmap(new HybridBinarizer(source));
+    }
+
+    /**
+     * Decode a binary bitmap.
+     *
+     * @param bitmap the binary bitmap
+     * @return a Result or null
+     */
+    protected Result decode(BinaryBitmap bitmap) {
+        possibleResultPoints.clear();
+        try {
+            if (reader instanceof MultiFormatReader) {
+                // Optimization - MultiFormatReader's normal decode() method is slow.
+                return ((MultiFormatReader) reader).decodeWithState(bitmap);
+            } else {
+                return reader.decode(bitmap);
+            }
+        } catch (Exception e) {
+            // Decode error, try again next frame
+            return null;
+        } finally {
+            reader.reset();
+        }
+    }
+
+    private List<ResultPoint> possibleResultPoints = new ArrayList<>();
+
+    /**
+     * Call immediately after decode(), from the same thread.
+     *
+     * The result is undefined while decode() is running.
+     *
+     * @return possible ResultPoints from the last decode.
+     */
+    public List<ResultPoint> getPossibleResultPoints() {
+        return new ArrayList<>(possibleResultPoints);
+    }
+
+    @Override
+    public void foundPossibleResultPoint(ResultPoint point) {
+        possibleResultPoints.add(point);
+    }
+}

+ 24 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/DecoderFactory.java

@@ -0,0 +1,24 @@
+package com.journeyapps.barcodescanner;
+
+import com.google.zxing.DecodeHintType;
+
+import java.util.Map;
+
+/**
+ * Factory to create Decoder instances. Typically one instance will be created per DecoderThread.
+ *
+ * @see DefaultDecoderFactory
+ */
+public interface DecoderFactory {
+
+    /**
+     * Create a new Decoder.
+     *
+     * While this method will only be called from a single thread, the created Decoder will
+     * be used from a different thread. Each decoder will only be used from a single thread.
+     *
+     * @param baseHints default hints. Typically specifies DecodeHintType.NEED_RESULT_POINT_CALLBACK.
+     * @return a new Decoder
+     */
+    Decoder createDecoder(Map<DecodeHintType, ?> baseHints);
+}

+ 33 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/DecoderResultPointCallback.java

@@ -0,0 +1,33 @@
+package com.journeyapps.barcodescanner;
+
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+
+/**
+ * ResultPointCallback delegating the ResultPoints to a decoder.
+ */
+public class DecoderResultPointCallback implements ResultPointCallback {
+    private Decoder decoder;
+
+    public DecoderResultPointCallback(Decoder decoder) {
+        this.decoder = decoder;
+    }
+
+    public DecoderResultPointCallback() {
+    }
+
+    public Decoder getDecoder() {
+        return decoder;
+    }
+
+    public void setDecoder(Decoder decoder) {
+        this.decoder = decoder;
+    }
+
+    @Override
+    public void foundPossibleResultPoint(ResultPoint point) {
+        if (decoder != null) {
+            decoder.foundPossibleResultPoint(point);
+        }
+    }
+}

+ 173 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/DecoderThread.java

@@ -0,0 +1,173 @@
+package com.journeyapps.barcodescanner;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Log;
+
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.Result;
+import com.google.zxing.ResultPoint;
+import com.journeyapps.barcodescanner.R;
+import com.journeyapps.barcodescanner.camera.CameraInstance;
+import com.journeyapps.barcodescanner.camera.PreviewCallback;
+
+import java.util.List;
+
+/**
+ *
+ */
+public class DecoderThread {
+    private static final String TAG = DecoderThread.class.getSimpleName();
+
+    private CameraInstance cameraInstance;
+    private HandlerThread thread;
+    private Handler handler;
+    private Decoder decoder;
+    private Handler resultHandler;
+    private Rect cropRect;
+    private boolean running = false;
+    private final Object LOCK = new Object();
+
+    private final Handler.Callback callback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message message) {
+            if (message.what == R.id.zxing_decode) {
+                decode((SourceData) message.obj);
+            } else if (message.what == R.id.zxing_preview_failed) {
+                // Error already logged. Try again.
+                requestNextPreview();
+            }
+            return true;
+        }
+    };
+
+    public DecoderThread(CameraInstance cameraInstance, Decoder decoder, Handler resultHandler) {
+        Util.validateMainThread();
+
+        this.cameraInstance = cameraInstance;
+        this.decoder = decoder;
+        this.resultHandler = resultHandler;
+    }
+
+    public Decoder getDecoder() {
+        return decoder;
+    }
+
+    public void setDecoder(Decoder decoder) {
+        this.decoder = decoder;
+    }
+
+    public Rect getCropRect() {
+        return cropRect;
+    }
+
+    public void setCropRect(Rect cropRect) {
+        this.cropRect = cropRect;
+    }
+
+    /**
+     * Start decoding.
+     *
+     * This must be called from the UI thread.
+     */
+    public void start() {
+        Util.validateMainThread();
+
+        thread = new HandlerThread(TAG);
+        thread.start();
+        handler = new Handler(thread.getLooper(), callback);
+        running = true;
+        requestNextPreview();
+    }
+
+    /**
+     * Stop decoding.
+     *
+     * This must be called from the UI thread.
+     */
+    public void stop() {
+        Util.validateMainThread();
+
+        synchronized (LOCK) {
+            running = false;
+            handler.removeCallbacksAndMessages(null);
+            thread.quit();
+        }
+    }
+
+    private final PreviewCallback previewCallback = new PreviewCallback() {
+        @Override
+        public void onPreview(SourceData sourceData) {
+            // Only post if running, to prevent a warning like this:
+            //   java.lang.RuntimeException: Handler (android.os.Handler) sending message to a Handler on a dead thread
+
+            // synchronize to handle cases where this is called concurrently with stop()
+            synchronized (LOCK) {
+                if (running) {
+                    // Post to our thread.
+                    handler.obtainMessage(R.id.zxing_decode, sourceData).sendToTarget();
+                }
+            }
+        }
+
+        @Override
+        public void onPreviewError(Exception e) {
+            synchronized (LOCK) {
+                if (running) {
+                    // Post to our thread.
+                    handler.obtainMessage(R.id.zxing_preview_failed).sendToTarget();
+                }
+            }
+        }
+    };
+
+    private void requestNextPreview() {
+        cameraInstance.requestPreview(previewCallback);
+    }
+
+    protected LuminanceSource createSource(SourceData sourceData) {
+        if (this.cropRect == null) {
+            return null;
+        } else {
+            return sourceData.createSource();
+        }
+    }
+
+    private void decode(SourceData sourceData) {
+        long start = System.currentTimeMillis();
+        Result rawResult = null;
+        sourceData.setCropRect(cropRect);
+        LuminanceSource source = createSource(sourceData);
+
+        if (source != null) {
+            rawResult = decoder.decode(source);
+        }
+
+        if (rawResult != null) {
+            // Don't log the barcode contents for security.
+            long end = System.currentTimeMillis();
+            Log.d(TAG, "Found barcode in " + (end - start) + " ms");
+            if (resultHandler != null) {
+                BarcodeResult barcodeResult = new BarcodeResult(rawResult, sourceData);
+                Message message = Message.obtain(resultHandler, R.id.zxing_decode_succeeded, barcodeResult);
+                Bundle bundle = new Bundle();
+                message.setData(bundle);
+                message.sendToTarget();
+            }
+        } else {
+            if (resultHandler != null) {
+                Message message = Message.obtain(resultHandler, R.id.zxing_decode_failed);
+                message.sendToTarget();
+            }
+        }
+        if (resultHandler != null) {
+            List<ResultPoint> resultPoints = BarcodeResult.transformResultPoints(decoder.getPossibleResultPoints(), sourceData);
+                    Message message = Message.obtain(resultHandler, R.id.zxing_possible_result_points, resultPoints);
+            message.sendToTarget();
+        }
+        requestNextPreview();
+    }
+}

+ 306 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/DecoratedBarcodeView.java

@@ -0,0 +1,306 @@
+package com.journeyapps.barcodescanner;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.client.android.DecodeFormatManager;
+import com.google.zxing.client.android.DecodeHintManager;
+import com.google.zxing.client.android.Intents;
+import com.journeyapps.barcodescanner.R;
+import com.journeyapps.barcodescanner.camera.CameraParametersCallback;
+import com.journeyapps.barcodescanner.camera.CameraSettings;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Encapsulates BarcodeView, ViewfinderView and status text.
+ *
+ * To customize the UI, use BarcodeView and ViewfinderView directly.
+ */
+public class DecoratedBarcodeView extends FrameLayout {
+    private BarcodeView barcodeView;
+    private ViewfinderView viewFinder;
+    private TextView statusView;
+
+    /**
+     * The instance of @link TorchListener to send events callback.
+     */
+    private TorchListener torchListener;
+
+    private class WrappedCallback implements BarcodeCallback {
+        private BarcodeCallback delegate;
+
+        public WrappedCallback(BarcodeCallback delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public void barcodeResult(BarcodeResult result) {
+            delegate.barcodeResult(result);
+        }
+
+        @Override
+        public void possibleResultPoints(List<ResultPoint> resultPoints) {
+            for (ResultPoint point : resultPoints) {
+                viewFinder.addPossibleResultPoint(point);
+            }
+            delegate.possibleResultPoints(resultPoints);
+        }
+    }
+
+    public DecoratedBarcodeView(Context context) {
+        super(context);
+        initialize();
+    }
+
+    public DecoratedBarcodeView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initialize(attrs);
+    }
+
+    public DecoratedBarcodeView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initialize(attrs);
+    }
+
+    /**
+     * Initialize the view with the xml configuration based on styleable attributes.
+     *
+     * @param attrs The attributes to use on view.
+     */
+    private void initialize(AttributeSet attrs) {
+        // Get attributes set on view
+        TypedArray attributes = getContext().obtainStyledAttributes(attrs, R.styleable.zxing_view);
+
+        int scannerLayout = attributes.getResourceId(
+                R.styleable.zxing_view_zxing_scanner_layout, R.layout.zxing_barcode_scanner);
+
+        attributes.recycle();
+
+        inflate(getContext(), scannerLayout, this);
+
+        barcodeView = findViewById(R.id.zxing_barcode_surface);
+
+        if (barcodeView == null) {
+            throw new IllegalArgumentException(
+                "There is no a com.journeyapps.barcodescanner.BarcodeView on provided layout " +
+                "with the id \"zxing_barcode_surface\".");
+        }
+
+        // Pass on any preview-related attributes
+        barcodeView.initializeAttributes(attrs);
+
+
+        viewFinder = findViewById(R.id.zxing_viewfinder_view);
+
+        if (viewFinder == null) {
+            throw new IllegalArgumentException(
+                "There is no a com.journeyapps.barcodescanner.ViewfinderView on provided layout " +
+                "with the id \"zxing_viewfinder_view\".");
+        }
+
+        viewFinder.setCameraPreview(barcodeView);
+
+        // statusView is optional
+        statusView = findViewById(R.id.zxing_status_view);
+    }
+
+    /**
+     * Initialize with no custom attributes set.
+     */
+    private void initialize() {
+        initialize(null);
+    }
+
+    /**
+     * Convenience method to initialize camera id, decode formats and prompt message from an intent.
+     *
+     * @param intent the intent, as generated by IntentIntegrator
+     */
+    public void initializeFromIntent(Intent intent) {
+        // Scan the formats the intent requested, and return the result to the calling activity.
+        Set<BarcodeFormat> decodeFormats = DecodeFormatManager.parseDecodeFormats(intent);
+        Map<DecodeHintType, Object> decodeHints = DecodeHintManager.parseDecodeHints(intent);
+
+        CameraSettings settings = new CameraSettings();
+
+        if (intent.hasExtra(Intents.Scan.CAMERA_ID)) {
+            int cameraId = intent.getIntExtra(Intents.Scan.CAMERA_ID, -1);
+            if (cameraId >= 0) {
+                settings.setRequestedCameraId(cameraId);
+            }
+        }
+
+        if (intent.hasExtra(Intents.Scan.TORCH_ENABLED)) {
+            if (intent.getBooleanExtra(Intents.Scan.TORCH_ENABLED, false)) {
+                this.setTorchOn();
+            }
+        }
+
+        String customPromptMessage = intent.getStringExtra(Intents.Scan.PROMPT_MESSAGE);
+        if (customPromptMessage != null) {
+            setStatusText(customPromptMessage);
+        }
+
+        // Check what type of scan. Default: normal scan
+        int scanType = intent.getIntExtra(Intents.Scan.SCAN_TYPE, 0);
+
+        String characterSet = intent.getStringExtra(Intents.Scan.CHARACTER_SET);
+
+        MultiFormatReader reader = new MultiFormatReader();
+        reader.setHints(decodeHints);
+
+        barcodeView.setCameraSettings(settings);
+        barcodeView.setDecoderFactory(new DefaultDecoderFactory(decodeFormats, decodeHints, characterSet, scanType));
+    }
+
+    public void setCameraSettings(CameraSettings cameraSettings) {
+        barcodeView.setCameraSettings(cameraSettings);
+    }
+
+    public void setDecoderFactory(DecoderFactory decoderFactory) {
+        barcodeView.setDecoderFactory(decoderFactory);
+    }
+
+    public DecoderFactory getDecoderFactory() {
+        return barcodeView.getDecoderFactory();
+    }
+
+    public CameraSettings getCameraSettings() {
+        return barcodeView.getCameraSettings();
+    }
+
+    public void setStatusText(String text) {
+        // statusView is optional when using a custom layout
+        if (statusView != null) {
+            statusView.setText(text);
+        }
+    }
+
+    /**
+     * @see BarcodeView#pause()
+     */
+    public void pause() {
+        barcodeView.pause();
+    }
+
+    /**
+     * @see BarcodeView#pauseAndWait()
+     */
+    public void pauseAndWait() {
+        barcodeView.pauseAndWait();
+    }
+
+    /**
+     * @see BarcodeView#resume()
+     */
+    public void resume() {
+        barcodeView.resume();
+    }
+
+    public BarcodeView getBarcodeView() {
+        return findViewById(R.id.zxing_barcode_surface);
+    }
+
+    public ViewfinderView getViewFinder() {
+        return viewFinder;
+    }
+
+    public TextView getStatusView() {
+        return statusView;
+    }
+
+    /**
+     * @see BarcodeView#decodeSingle(BarcodeCallback)
+     */
+    public void decodeSingle(BarcodeCallback callback) {
+        barcodeView.decodeSingle(new WrappedCallback(callback));
+    }
+
+    /**
+     * @see BarcodeView#decodeContinuous(BarcodeCallback)
+     */
+    public void decodeContinuous(BarcodeCallback callback) {
+        barcodeView.decodeContinuous(new WrappedCallback(callback));
+    }
+
+    /**
+     * Turn on the device's flashlight.
+     */
+    public void setTorchOn() {
+        barcodeView.setTorch(true);
+
+        if (torchListener != null) {
+            torchListener.onTorchOn();
+        }
+    }
+
+    /**
+     * Turn off the device's flashlight.
+     */
+    public void setTorchOff() {
+        barcodeView.setTorch(false);
+
+        if (torchListener != null) {
+            torchListener.onTorchOff();
+        }
+    }
+
+    /**
+     * Changes the settings for Camera.
+     * Must be called after {@link #resume()}.
+     *
+     * @param callback {@link CameraParametersCallback}
+     */
+    public void changeCameraParameters(CameraParametersCallback callback) {
+        barcodeView.changeCameraParameters(callback);
+    }
+
+    /**
+     * Handles focus, camera, volume up and volume down keys.
+     *
+     * Note that this view is not usually focused, so the Activity should call this directly.
+     */
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_FOCUS:
+            case KeyEvent.KEYCODE_CAMERA:
+                // Handle these events so they don't launch the Camera app
+                return true;
+            // Use volume up/down to turn on light
+            case KeyEvent.KEYCODE_VOLUME_DOWN:
+                setTorchOff();
+                return true;
+            case KeyEvent.KEYCODE_VOLUME_UP:
+                setTorchOn();
+                return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    public void setTorchListener(TorchListener listener) {
+        this.torchListener = listener;
+    }
+
+    /**
+     * The Listener to torch/fflashlight events (turn on, turn off).
+     */
+    public interface TorchListener {
+
+        void onTorchOn();
+
+        void onTorchOff();
+    }
+}

+ 72 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/DefaultDecoderFactory.java

@@ -0,0 +1,72 @@
+package com.journeyapps.barcodescanner;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.MultiFormatReader;
+
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * DecoderFactory that creates a MultiFormatReader with specified hints.
+ */
+public class DefaultDecoderFactory implements DecoderFactory {
+    private Collection<BarcodeFormat> decodeFormats;
+    private Map<DecodeHintType, ?> hints;
+    private String characterSet;
+    private int scanType;
+
+    public DefaultDecoderFactory() {
+    }
+
+
+
+    public DefaultDecoderFactory(Collection<BarcodeFormat> decodeFormats) {
+        this.decodeFormats = decodeFormats;
+    }
+
+    public DefaultDecoderFactory(Collection<BarcodeFormat> decodeFormats, Map<DecodeHintType, ?> hints, String characterSet, int scanType) {
+        this.decodeFormats = decodeFormats;
+        this.hints = hints;
+        this.characterSet = characterSet;
+        this.scanType = scanType;
+    }
+
+    @Override
+    public Decoder createDecoder(Map<DecodeHintType, ?> baseHints) {
+        Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
+
+        hints.putAll(baseHints);
+
+        if (this.hints != null) {
+            hints.putAll(this.hints);
+        }
+
+        if (this.decodeFormats != null) {
+            hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
+        }
+
+        if (characterSet != null) {
+            hints.put(DecodeHintType.CHARACTER_SET, characterSet);
+        }
+
+        MultiFormatReader reader = new MultiFormatReader();
+        reader.setHints(hints);
+
+        switch (scanType){
+            case 0:
+                return new Decoder(reader);
+            case 1:
+                return new InvertedDecoder(reader);
+            case 2:
+                return new MixedDecoder(reader);
+            default:
+                return new Decoder(reader);
+
+
+        }
+
+
+    }
+}

+ 36 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/InvertedDecoder.java

@@ -0,0 +1,36 @@
+package com.journeyapps.barcodescanner;
+
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.Reader;
+import com.google.zxing.common.HybridBinarizer;
+
+/**
+ * Created by leighmd on 11/2/16.
+ */
+public class InvertedDecoder extends Decoder {
+
+    /**
+     * Create a new Decoder with the specified Reader.
+     * <p/>
+     * It is recommended to use an instance of MultiFormatReader in most cases.
+     *
+     * @param reader the reader
+     */
+    public InvertedDecoder(Reader reader) {
+        super(reader);
+    }
+
+    /**
+     * Given an image source, convert to a binary bitmap.
+     *
+     * Override this to use a custom binarizer.
+     *
+     * @param source the image source
+     * @return a BinaryBitmap
+     */
+    protected BinaryBitmap toBitmap(LuminanceSource source) {
+
+        return new BinaryBitmap(new HybridBinarizer(source.invert()));
+    }
+}

+ 43 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/MixedDecoder.java

@@ -0,0 +1,43 @@
+package com.journeyapps.barcodescanner;
+
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.Reader;
+import com.google.zxing.common.HybridBinarizer;
+
+/**
+ * Decoder that performs alternating scans in normal mode and inverted mode.
+ */
+public class MixedDecoder extends Decoder {
+    private boolean isInverted = true;
+
+    /**
+     * Create a new Decoder with the specified Reader.
+     * <p/>
+     * It is recommended to use an instance of MultiFormatReader in most cases.
+     *
+     * @param reader the reader
+     */
+    public MixedDecoder(Reader reader) {
+        super(reader);
+    }
+
+    /**
+     * Given an image source, convert to a binary bitmap.
+     *
+     * Override this to use a custom binarizer.
+     *
+     * @param source the image source
+     * @return a BinaryBitmap
+     */
+    protected BinaryBitmap toBitmap(LuminanceSource source) {
+        if (isInverted) {
+            isInverted = false;
+            return new BinaryBitmap(new HybridBinarizer(source.invert()));
+        } else {
+            isInverted = true;
+            return new BinaryBitmap(new HybridBinarizer(source));
+        }
+    }
+
+}

+ 142 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/RawImageData.java

@@ -0,0 +1,142 @@
+package com.journeyapps.barcodescanner;
+
+import android.graphics.Rect;
+
+public class RawImageData {
+    private byte[] data;
+    private int width;
+    private int height;
+
+    public RawImageData(byte[] data, int width, int height) {
+        this.data = data;
+        this.width = width;
+        this.height = height;
+    }
+
+    public byte[] getData() {
+        return data;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public RawImageData cropAndScale(Rect cropRect, int scale) {
+        int width = cropRect.width() / scale;
+        int height = cropRect.height() / scale;
+
+        int top = cropRect.top;
+
+        int area = width * height;
+        byte[] matrix = new byte[area];
+
+        if (scale == 1) {
+            int inputOffset = top * this.width + cropRect.left;
+
+            // Copy one cropped row at a time.
+            for (int y = 0; y < height; y++) {
+                int outputOffset = y * width;
+                System.arraycopy(this.data, inputOffset, matrix, outputOffset, width);
+                inputOffset += this.width;
+            }
+        } else {
+            int inputOffset = top * this.width + cropRect.left;
+
+            // Copy one cropped row at a time.
+            for (int y = 0; y < height; y++) {
+                int outputOffset = y * width;
+                int xOffset = inputOffset;
+                for (int x = 0; x < width; x++) {
+                    matrix[outputOffset] = this.data[xOffset];
+                    xOffset += scale;
+                    outputOffset += 1;
+                }
+                inputOffset += this.width * scale;
+            }
+        }
+        return new RawImageData(matrix, width, height);
+    }
+
+
+    public RawImageData rotateCameraPreview(int cameraRotation) {
+        switch (cameraRotation) {
+            case 90:
+                return new RawImageData(rotateCW(data, this.width, this.height), this.height, this.width);
+            case 180:
+                return new RawImageData(rotate180(data, this.width, this.height), this.width, this.height);
+            case 270:
+                return new RawImageData(rotateCCW(data, this.width, this.height), this.height, this.width);
+            case 0:
+            default:
+                return this;
+        }
+    }
+
+    /**
+     * Rotate an image by 90 degrees CW.
+     *
+     * @param data        the image data, in with the first width * height bytes being the luminance data.
+     * @param imageWidth  the width of the image
+     * @param imageHeight the height of the image
+     * @return the rotated bytes
+     */
+    public static byte[] rotateCW(byte[] data, int imageWidth, int imageHeight) {
+        // Adapted from http://stackoverflow.com/a/15775173
+        // data may contain more than just y (u and v), but we are only interested in the y section.
+
+        byte[] yuv = new byte[imageWidth * imageHeight];
+        int i = 0;
+        for (int x = 0; x < imageWidth; x++) {
+            for (int y = imageHeight - 1; y >= 0; y--) {
+                yuv[i] = data[y * imageWidth + x];
+                i++;
+            }
+        }
+        return yuv;
+    }
+
+    /**
+     * Rotate an image by 180 degrees.
+     *
+     * @param data        the image data, in with the first width * height bytes being the luminance data.
+     * @param imageWidth  the width of the image
+     * @param imageHeight the height of the image
+     * @return the rotated bytes
+     */
+    public static byte[] rotate180(byte[] data, int imageWidth, int imageHeight) {
+        int n = imageWidth * imageHeight;
+        byte[] yuv = new byte[n];
+
+        int i = n - 1;
+        for (int j = 0; j < n; j++) {
+            yuv[i] = data[j];
+            i--;
+        }
+        return yuv;
+    }
+
+    /**
+     * Rotate an image by 90 degrees CCW.
+     *
+     * @param data        the image data, in with the first width * height bytes being the luminance data.
+     * @param imageWidth  the width of the image
+     * @param imageHeight the height of the image
+     * @return the rotated bytes
+     */
+    public static byte[] rotateCCW(byte[] data, int imageWidth, int imageHeight) {
+        int n = imageWidth * imageHeight;
+        byte[] yuv = new byte[n];
+        int i = n - 1;
+        for (int x = 0; x < imageWidth; x++) {
+            for (int y = imageHeight - 1; y >= 0; y--) {
+                yuv[i] = data[y * imageWidth + x];
+                i--;
+            }
+        }
+        return yuv;
+    }
+}

+ 13 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/RotationCallback.java

@@ -0,0 +1,13 @@
+package com.journeyapps.barcodescanner;
+
+/**
+ *
+ */
+public interface RotationCallback {
+    /**
+     * Rotation changed.
+     *
+     * @param rotation the current value of windowManager.getDefaultDisplay().getRotation()
+     */
+    void onRotationChanged(int rotation);
+}

+ 67 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/RotationListener.java

@@ -0,0 +1,67 @@
+package com.journeyapps.barcodescanner;
+
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.view.OrientationEventListener;
+import android.view.WindowManager;
+
+/**
+ * Hack to detect when screen rotation is reversed, since that does not cause a configuration change.
+ *
+ * If it is changed through something other than the sensor (e.g. programmatically), this may not work.
+ *
+ * See http://stackoverflow.com/q/9909037
+ */
+public class RotationListener {
+    private int lastRotation;
+
+    private WindowManager windowManager;
+    private OrientationEventListener orientationEventListener;
+    private RotationCallback callback;
+
+    public RotationListener() {
+    }
+
+    public void listen(Context context, RotationCallback callback) {
+        // Stop to make sure we're not registering the listening twice.
+        stop();
+
+        // Only use the ApplicationContext. In case of a memory leak (e.g. from a framework bug),
+        // this will result in less being leaked.
+        context = context.getApplicationContext();
+
+        this.callback = callback;
+
+        this.windowManager = (WindowManager) context
+                .getSystemService(Context.WINDOW_SERVICE);
+
+        this.orientationEventListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) {
+            @Override
+            public void onOrientationChanged(int orientation) {
+                WindowManager localWindowManager = windowManager;
+                RotationCallback localCallback = RotationListener.this.callback;
+                if (windowManager != null && localCallback != null) {
+                    int newRotation = localWindowManager.getDefaultDisplay().getRotation();
+                    if (newRotation != lastRotation) {
+                        lastRotation = newRotation;
+                        localCallback.onRotationChanged(newRotation);
+                    }
+                }
+            }
+        };
+        this.orientationEventListener.enable();
+
+        lastRotation = windowManager.getDefaultDisplay().getRotation();
+    }
+
+    public void stop() {
+        // To reduce the effect of possible leaks, we clear any references we have to external
+        // objects.
+        if (this.orientationEventListener != null) {
+            this.orientationEventListener.disable();
+        }
+        this.orientationEventListener = null;
+        this.windowManager = null;
+        this.callback = null;
+    }
+}

+ 21 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/ScanContract.java

@@ -0,0 +1,21 @@
+package com.journeyapps.barcodescanner;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.activity.result.contract.ActivityResultContract;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class ScanContract extends ActivityResultContract<ScanOptions, ScanIntentResult> {
+    @NonNull
+    @Override
+    public Intent createIntent(@NonNull Context context, ScanOptions input) {
+        return input.createScanIntent(context);
+    }
+
+    @Override
+    public ScanIntentResult parseResult(int resultCode, @Nullable Intent intent) {
+        return ScanIntentResult.parseActivityResult(resultCode, intent);
+    }
+}

+ 155 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/ScanIntentResult.java

@@ -0,0 +1,155 @@
+/*
+ * Based on IntentResult.
+ *
+ * Copyright 2009 ZXing authors
+ *
+ * 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.journeyapps.barcodescanner;
+
+import android.app.Activity;
+import android.content.Intent;
+
+import com.google.zxing.client.android.Intents;
+import com.google.zxing.integration.android.IntentResult;
+
+/**
+ * <p>Encapsulates the result of a barcode scan invoked through {@link ScanContract}.</p>
+ *
+ * @author Sean Owen
+ */
+public final class ScanIntentResult {
+
+    private final String contents;
+    private final String formatName;
+    private final byte[] rawBytes;
+    private final Integer orientation;
+    private final String errorCorrectionLevel;
+    private final String barcodeImagePath;
+    private final Intent originalIntent;
+
+    ScanIntentResult() {
+        this(null, null, null, null, null, null, null);
+    }
+
+    ScanIntentResult(Intent intent) {
+        this(null, null, null, null, null, null, intent);
+    }
+
+    ScanIntentResult(String contents,
+                     String formatName,
+                     byte[] rawBytes,
+                     Integer orientation,
+                     String errorCorrectionLevel,
+                     String barcodeImagePath,
+                     Intent originalIntent) {
+        this.contents = contents;
+        this.formatName = formatName;
+        this.rawBytes = rawBytes;
+        this.orientation = orientation;
+        this.errorCorrectionLevel = errorCorrectionLevel;
+        this.barcodeImagePath = barcodeImagePath;
+        this.originalIntent = originalIntent;
+    }
+
+    /**
+     * @return raw content of barcode
+     */
+    public String getContents() {
+        return contents;
+    }
+
+    /**
+     * @return name of format, like "QR_CODE", "UPC_A". See {@code BarcodeFormat} for more format names.
+     */
+    public String getFormatName() {
+        return formatName;
+    }
+
+    /**
+     * @return raw bytes of the barcode content, if applicable, or null otherwise
+     */
+    public byte[] getRawBytes() {
+        return rawBytes;
+    }
+
+    /**
+     * @return rotation of the image, in degrees, which resulted in a successful scan. May be null.
+     */
+    public Integer getOrientation() {
+        return orientation;
+    }
+
+    /**
+     * @return name of the error correction level used in the barcode, if applicable
+     */
+    public String getErrorCorrectionLevel() {
+        return errorCorrectionLevel;
+    }
+
+    /**
+     * @return path to a temporary file containing the barcode image, if applicable, or null otherwise
+     */
+    public String getBarcodeImagePath() {
+        return barcodeImagePath;
+    }
+
+    /**
+     * @return the original intent
+     */
+    public Intent getOriginalIntent() {
+        return originalIntent;
+    }
+
+    @Override
+    public String toString() {
+        int rawBytesLength = rawBytes == null ? 0 : rawBytes.length;
+        return "Format: " + formatName + '\n' +
+            "Contents: " + contents + '\n' +
+            "Raw bytes: (" + rawBytesLength + " bytes)\n" +
+            "Orientation: " + orientation + '\n' +
+            "EC level: " + errorCorrectionLevel + '\n' +
+            "Barcode image: " + barcodeImagePath + '\n' +
+            "Original intent: " + originalIntent + '\n';
+    }
+
+
+    /**
+     * Parse activity result, without checking the request code.
+     *
+     * @param resultCode result code from {@code onActivityResult()}
+     * @param intent     {@link Intent} from {@code onActivityResult()}
+     * @return an {@link IntentResult} containing the result of the scan. If the user cancelled scanning,
+     * the fields will be null.
+     */
+    public static ScanIntentResult parseActivityResult(int resultCode, Intent intent) {
+        if (resultCode == Activity.RESULT_OK) {
+            String contents = intent.getStringExtra(Intents.Scan.RESULT);
+            String formatName = intent.getStringExtra(Intents.Scan.RESULT_FORMAT);
+            byte[] rawBytes = intent.getByteArrayExtra(Intents.Scan.RESULT_BYTES);
+            int intentOrientation = intent.getIntExtra(Intents.Scan.RESULT_ORIENTATION, Integer.MIN_VALUE);
+            Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation;
+            String errorCorrectionLevel = intent.getStringExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL);
+            String barcodeImagePath = intent.getStringExtra(Intents.Scan.RESULT_BARCODE_IMAGE_PATH);
+            return new ScanIntentResult(contents,
+                    formatName,
+                    rawBytes,
+                    orientation,
+                    errorCorrectionLevel,
+                    barcodeImagePath,
+                    intent);
+        }
+        return new ScanIntentResult(intent);
+    }
+}

+ 262 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/ScanOptions.java

@@ -0,0 +1,262 @@
+/*
+ * Based on IntentIntegrator, Copyright 2009 ZXing authors.
+ *
+ */
+package com.journeyapps.barcodescanner;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.google.zxing.client.android.Intents;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ScanOptions {
+    // supported barcode formats
+
+    // Product Codes
+    public static final String UPC_A = "UPC_A";
+    public static final String UPC_E = "UPC_E";
+    public static final String EAN_8 = "EAN_8";
+    public static final String EAN_13 = "EAN_13";
+    public static final String RSS_14 = "RSS_14";
+
+    // Other 1D
+    public static final String CODE_39 = "CODE_39";
+    public static final String CODE_93 = "CODE_93";
+    public static final String CODE_128 = "CODE_128";
+    public static final String ITF = "ITF";
+
+    public static final String RSS_EXPANDED = "RSS_EXPANDED";
+
+    // 2D
+    public static final String QR_CODE = "QR_CODE";
+    public static final String DATA_MATRIX = "DATA_MATRIX";
+    public static final String PDF_417 = "PDF_417";
+
+
+    public static final Collection<String> PRODUCT_CODE_TYPES = list(UPC_A, UPC_E, EAN_8, EAN_13, RSS_14);
+    public static final Collection<String> ONE_D_CODE_TYPES =
+            list(UPC_A, UPC_E, EAN_8, EAN_13, RSS_14, CODE_39, CODE_93, CODE_128,
+                    ITF, RSS_14, RSS_EXPANDED);
+
+    public static final Collection<String> ALL_CODE_TYPES = null;
+
+    private final Map<String, Object> moreExtras = new HashMap<>(3);
+
+    private Collection<String> desiredBarcodeFormats;
+
+    private Class<?> captureActivity;
+
+
+    protected Class<?> getDefaultCaptureActivity() {
+        return CaptureActivity.class;
+    }
+
+    public ScanOptions() {
+
+    }
+
+
+    public Class<?> getCaptureActivity() {
+        if (captureActivity == null) {
+            captureActivity = getDefaultCaptureActivity();
+        }
+        return captureActivity;
+    }
+
+    /**
+     * Set the Activity class to use. It can be any activity, but should handle the intent extras
+     * as used here.
+     *
+     * @param captureActivity the class
+     */
+    public ScanOptions setCaptureActivity(Class<?> captureActivity) {
+        this.captureActivity = captureActivity;
+        return this;
+    }
+
+    public Map<String, ?> getMoreExtras() {
+        return moreExtras;
+    }
+
+    public final ScanOptions addExtra(String key, Object value) {
+        moreExtras.put(key, value);
+        return this;
+    }
+
+    /**
+     * Set a prompt to display on the capture screen, instead of using the default.
+     *
+     * @param prompt the prompt to display
+     */
+    public final ScanOptions setPrompt(String prompt) {
+        if (prompt != null) {
+            addExtra(Intents.Scan.PROMPT_MESSAGE, prompt);
+        }
+        return this;
+    }
+
+    /**
+     * By default, the orientation is locked. Set to false to not lock.
+     *
+     * @param locked true to lock orientation
+     */
+    public ScanOptions setOrientationLocked(boolean locked) {
+        addExtra(Intents.Scan.ORIENTATION_LOCKED, locked);
+        return this;
+    }
+
+    /**
+     * Use the specified camera ID.
+     *
+     * @param cameraId camera ID of the camera to use. A negative value means "no preference".
+     * @return this
+     */
+    public ScanOptions setCameraId(int cameraId) {
+        if (cameraId >= 0) {
+            addExtra(Intents.Scan.CAMERA_ID, cameraId);
+        }
+        return this;
+    }
+
+    /**
+     * Set to true to enable initial torch
+     *
+     * @param enabled true to enable initial torch
+     * @return this
+     */
+    public ScanOptions setTorchEnabled(boolean enabled) {
+        addExtra(Intents.Scan.TORCH_ENABLED, enabled);
+        return this;
+    }
+
+
+    /**
+     * Set to false to disable beep on scan.
+     *
+     * @param enabled false to disable beep
+     * @return this
+     */
+    public ScanOptions setBeepEnabled(boolean enabled) {
+        addExtra(Intents.Scan.BEEP_ENABLED, enabled);
+        return this;
+    }
+
+    /**
+     * Set to true to enable saving the barcode image and sending its path in the result Intent.
+     *
+     * @param enabled true to enable barcode image
+     * @return this
+     */
+    public ScanOptions setBarcodeImageEnabled(boolean enabled) {
+        addExtra(Intents.Scan.BARCODE_IMAGE_ENABLED, enabled);
+        return this;
+    }
+
+    /**
+     * Set the desired barcode formats to scan.
+     *
+     * @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
+     * @return this
+     */
+    public ScanOptions setDesiredBarcodeFormats(Collection<String> desiredBarcodeFormats) {
+        this.desiredBarcodeFormats = desiredBarcodeFormats;
+        return this;
+    }
+
+    /**
+     * Set the desired barcode formats to scan.
+     *
+     * @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
+     * @return this
+     */
+    public ScanOptions setDesiredBarcodeFormats(String... desiredBarcodeFormats) {
+        this.desiredBarcodeFormats = Arrays.asList(desiredBarcodeFormats);
+        return this;
+    }
+
+    /**
+     * Initiates a scan for all known barcode types with the default camera.
+     * And starts a timer to finish on timeout
+     *
+     * @return Activity.RESULT_CANCELED and true on parameter TIMEOUT.
+     */
+    public ScanOptions setTimeout(long timeout) {
+        addExtra(Intents.Scan.TIMEOUT, timeout);
+        return this;
+    }
+
+    /**
+     * Create an scan intent with the specified options.
+     *
+     * @return the intent
+     */
+    public Intent createScanIntent(Context context) {
+        Intent intentScan = new Intent(context, getCaptureActivity());
+        intentScan.setAction(Intents.Scan.ACTION);
+
+        // check which types of codes to scan for
+        if (desiredBarcodeFormats != null) {
+            // set the desired barcode types
+            StringBuilder joinedByComma = new StringBuilder();
+            for (String format : desiredBarcodeFormats) {
+                if (joinedByComma.length() > 0) {
+                    joinedByComma.append(',');
+                }
+                joinedByComma.append(format);
+            }
+            intentScan.putExtra(Intents.Scan.FORMATS, joinedByComma.toString());
+        }
+
+        intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+        attachMoreExtras(intentScan);
+        return intentScan;
+    }
+
+    private static List<String> list(String... values) {
+        return Collections.unmodifiableList(Arrays.asList(values));
+    }
+
+    private void attachMoreExtras(Intent intent) {
+        for (Map.Entry<String, Object> entry : moreExtras.entrySet()) {
+            String key = entry.getKey();
+            Object value = entry.getValue();
+            // Kind of hacky
+            if (value instanceof Integer) {
+                intent.putExtra(key, (Integer) value);
+            } else if (value instanceof Long) {
+                intent.putExtra(key, (Long) value);
+            } else if (value instanceof Boolean) {
+                intent.putExtra(key, (Boolean) value);
+            } else if (value instanceof Double) {
+                intent.putExtra(key, (Double) value);
+            } else if (value instanceof Float) {
+                intent.putExtra(key, (Float) value);
+            } else if (value instanceof Bundle) {
+                intent.putExtra(key, (Bundle) value);
+            } else if (value instanceof int[]) {
+                intent.putExtra(key, (int[]) value);
+            } else if (value instanceof long[]) {
+                intent.putExtra(key, (long[]) value);
+            } else if (value instanceof boolean[]) {
+                intent.putExtra(key, (boolean[]) value);
+            } else if (value instanceof double[]) {
+                intent.putExtra(key, (double[]) value);
+            } else if (value instanceof float[]) {
+                intent.putExtra(key, (float[]) value);
+            } else if (value instanceof String[]) {
+                intent.putExtra(key, (String[]) value);
+            } else {
+                intent.putExtra(key, value.toString());
+            }
+        }
+    }
+}

+ 117 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/Size.java

@@ -0,0 +1,117 @@
+package com.journeyapps.barcodescanner;
+
+import androidx.annotation.NonNull;
+
+/**
+ *
+ */
+public class Size implements Comparable<Size> {
+    public final int width;
+    public final int height;
+
+    public Size(int width, int height) {
+        this.width = width;
+        this.height = height;
+    }
+
+    /**
+     * Swap width and height.
+     *
+     * @return a new Size with swapped width and height
+     */
+    public Size rotate() {
+        //noinspection SuspiciousNameCombination
+        return new Size(height, width);
+    }
+
+    /**
+     * Scale by n / d.
+     *
+     * @param n numerator
+     * @param d denominator
+     * @return the scaled size
+     */
+    public Size scale(int n, int d) {
+        return new Size(width * n / d, height * n / d);
+    }
+
+    /**
+     * Scales the dimensions so that it fits entirely inside the parent.One of width or height will
+     * fit exactly. Aspect ratio is preserved.
+     *
+     * @param into the parent to fit into
+     * @return the scaled size
+     */
+    public Size scaleFit(Size into) {
+        if (width * into.height >= into.width * height) {
+            // match width
+            return new Size(into.width, height * into.width / width);
+        } else {
+            // match height
+            return new Size(width * into.height / height, into.height);
+        }
+    }
+    /**
+     * Scales the size so that both dimensions will be greater than or equal to the corresponding
+     * dimension of the parent. One of width or height will fit exactly. Aspect ratio is preserved.
+     *
+     * @param into the parent to fit into
+     * @return the scaled size
+     */
+    public Size scaleCrop(Size into) {
+        if (width * into.height <= into.width * height) {
+            // match width
+            return new Size(into.width, height * into.width / width);
+        } else {
+            // match height
+            return new Size(width * into.height / height, into.height);
+        }
+    }
+
+    /**
+     * Checks if both dimensions of the other size are at least as large as this size.
+     *
+     * @param other the size to compare with
+     * @return true if this size fits into the other size
+     */
+    public boolean fitsIn(Size other) {
+        return width <= other.width && height <= other.height;
+    }
+
+    /**
+     * Default sort order is ascending by size.
+     */
+    @Override
+    public int compareTo(@NonNull Size other) {
+        int aPixels = this.height * this.width;
+        int bPixels = other.height * other.width;
+        if (bPixels < aPixels) {
+            return 1;
+        }
+        if (bPixels > aPixels) {
+            return -1;
+        }
+        return 0;
+    }
+
+    public String toString() {
+        return width + "x" + height;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Size size = (Size) o;
+
+        return width == size.width && height == size.height;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = width;
+        result = 31 * result + height;
+        return result;
+    }
+}

+ 178 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/SourceData.java

@@ -0,0 +1,178 @@
+package com.journeyapps.barcodescanner;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+
+import com.google.zxing.PlanarYUVLuminanceSource;
+import com.google.zxing.ResultPoint;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Raw preview data from a camera.
+ */
+public class SourceData {
+    private RawImageData data;
+
+    /** The format of the image data. ImageFormat.NV21 and ImageFormat.YUY2 are supported. */
+    private int imageFormat;
+
+    /** Rotation in degrees (0, 90, 180 or 270). This is camera rotation relative to display rotation. */
+    private int rotation;
+
+    /** Crop rectangle, in display orientation. */
+    private Rect cropRect;
+
+    /**
+     * Factor by which to scale down before decoding.
+     */
+    private int scalingFactor = 1;
+
+    private boolean previewMirrored;
+
+    /**
+     *
+     * @param data the image data
+     * @param dataWidth width of the data
+     * @param dataHeight height of the data
+     * @param imageFormat ImageFormat.NV21 or ImageFormat.YUY2
+     * @param rotation camera rotation relative to display rotation, in degrees (0, 90, 180 or 270).
+     */
+    public SourceData(byte[] data, int dataWidth, int dataHeight, int imageFormat, int rotation) {
+        this.data = new RawImageData(data, dataWidth, dataHeight);
+        this.rotation = rotation;
+        this.imageFormat = imageFormat;
+        if (dataWidth * dataHeight > data.length) {
+            throw new IllegalArgumentException("Image data does not match the resolution. " + dataWidth + "x" + dataHeight + " > " + data.length);
+        }
+    }
+
+    public Rect getCropRect() {
+        return cropRect;
+    }
+
+    /**
+     * Set the crop rectangle.
+     *
+     * @param cropRect the new crop rectangle.
+     */
+    public void setCropRect(Rect cropRect) {
+        this.cropRect = cropRect;
+    }
+
+    public boolean isPreviewMirrored() {
+        return previewMirrored;
+    }
+
+    public void setPreviewMirrored(boolean previewMirrored) {
+        this.previewMirrored = previewMirrored;
+    }
+
+    public int getScalingFactor() {
+        return scalingFactor;
+    }
+
+    public void setScalingFactor(int scalingFactor) {
+        this.scalingFactor = scalingFactor;
+    }
+
+    public byte[] getData() {
+        return data.getData();
+    }
+
+    /**
+     *
+     * @return width of the data
+     */
+    public int getDataWidth() {
+        return data.getWidth();
+    }
+
+    /**
+     *
+     * @return height of the data
+     */
+    public int getDataHeight() {
+        return data.getHeight();
+    }
+
+    public ResultPoint translateResultPoint(ResultPoint point) {
+        float x = point.getX() * this.scalingFactor + this.cropRect.left;
+        float y = point.getY() * this.scalingFactor + this.cropRect.top;
+        if (previewMirrored) {
+            x = data.getWidth() - x;
+        }
+        return new ResultPoint(x, y);
+    }
+
+    /**
+     *
+     * @return true if the preview image is rotated orthogonal to the display
+     */
+    public boolean isRotated() {
+        return rotation % 180 != 0;
+    }
+
+    public int getImageFormat() {
+        return imageFormat;
+    }
+
+    public PlanarYUVLuminanceSource createSource() {
+        RawImageData rotated = this.data.rotateCameraPreview(rotation);
+        RawImageData scaled = rotated.cropAndScale(this.cropRect, this.scalingFactor);
+
+        // not the preview for decoding.
+        return new PlanarYUVLuminanceSource(scaled.getData(), scaled.getWidth(), scaled.getHeight(), 0, 0, scaled.getWidth(), scaled.getHeight(), false);
+    }
+
+    /**
+     * Return the source bitmap (cropped; in display orientation).
+     *
+     * @return the bitmap
+     */
+    public Bitmap getBitmap() {
+        return getBitmap(1);
+    }
+
+    /**
+     * Return the source bitmap (cropped; in display orientation).
+     *
+     * @param scaleFactor factor to scale down by. Must be a power of 2.
+     * @return the bitmap
+     */
+    public Bitmap getBitmap(int scaleFactor) {
+        return getBitmap(cropRect, scaleFactor);
+    }
+
+    public Bitmap getBitmap(Rect cropRect, int scaleFactor) {
+        if (cropRect == null) {
+            cropRect = new Rect(0, 0, data.getWidth(), data.getHeight());
+        } else if (isRotated()) {
+            //noinspection SuspiciousNameCombination
+            cropRect = new Rect(cropRect.top, cropRect.left, cropRect.bottom, cropRect.right);
+        }
+
+        // TODO: there should be a way to do this without JPEG compression / decompression cycle.
+        YuvImage img = new YuvImage(data.getData(), imageFormat, data.getWidth(), data.getHeight(), null);
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        img.compressToJpeg(cropRect, 90, buffer);
+        byte[] jpegData = buffer.toByteArray();
+
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inSampleSize = scaleFactor;
+        Bitmap bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options);
+
+        // Rotate if required
+        if (rotation != 0) {
+            Matrix imageMatrix = new Matrix();
+            imageMatrix.postRotate(rotation);
+            bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), imageMatrix, false);
+        }
+
+        return bitmap;
+    }
+
+}

+ 14 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/Util.java

@@ -0,0 +1,14 @@
+package com.journeyapps.barcodescanner;
+
+import android.os.Looper;
+
+/**
+ *
+ */
+public class Util {
+    public static void validateMainThread() {
+        if (Looper.getMainLooper() != Looper.myLooper()) {
+            throw new IllegalStateException("Must be called from the main thread.");
+        }
+    }
+}

+ 259 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/ViewfinderView.java

@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2008 ZXing authors
+ *
+ * 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.journeyapps.barcodescanner;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.google.zxing.ResultPoint;
+import com.journeyapps.barcodescanner.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This view is overlaid on top of the camera preview. It adds the viewfinder rectangle and partial
+ * transparency outside it, as well as the laser scanner animation and result points.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public class ViewfinderView extends View {
+    protected static final String TAG = ViewfinderView.class.getSimpleName();
+
+    protected static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64};
+    protected static final long ANIMATION_DELAY = 80L;
+    protected static final int CURRENT_POINT_OPACITY = 0xA0;
+    protected static final int MAX_RESULT_POINTS = 20;
+    protected static final int POINT_SIZE = 6;
+
+    protected final Paint paint;
+    protected Bitmap resultBitmap;
+    protected int maskColor;
+    protected final int resultColor;
+    protected final int laserColor;
+    protected final int resultPointColor;
+    protected boolean laserVisibility;
+    protected int scannerAlpha;
+    protected List<ResultPoint> possibleResultPoints;
+    protected List<ResultPoint> lastPossibleResultPoints;
+    protected CameraPreview cameraPreview;
+
+    // Cache the framingRect and previewSize, so that we can still draw it after the preview
+    // stopped.
+    protected Rect framingRect;
+    protected Size previewSize;
+
+    // This constructor is used when the class is built from an XML resource.
+    public ViewfinderView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // Initialize these once for performance rather than calling them every time in onDraw().
+        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+        Resources resources = getResources();
+
+        // Get set attributes on view
+        TypedArray attributes = getContext().obtainStyledAttributes(attrs, R.styleable.zxing_finder);
+
+        this.maskColor = attributes.getColor(R.styleable.zxing_finder_zxing_viewfinder_mask,
+                resources.getColor(R.color.zxing_viewfinder_mask));
+        this.resultColor = attributes.getColor(R.styleable.zxing_finder_zxing_result_view,
+                resources.getColor(R.color.zxing_result_view));
+        this.laserColor = attributes.getColor(R.styleable.zxing_finder_zxing_viewfinder_laser,
+                resources.getColor(R.color.zxing_viewfinder_laser));
+        this.resultPointColor = attributes.getColor(R.styleable.zxing_finder_zxing_possible_result_points,
+                resources.getColor(R.color.zxing_possible_result_points));
+        this.laserVisibility = attributes.getBoolean(R.styleable.zxing_finder_zxing_viewfinder_laser_visibility,
+                true);
+
+        attributes.recycle();
+
+        scannerAlpha = 0;
+        possibleResultPoints = new ArrayList<>(MAX_RESULT_POINTS);
+        lastPossibleResultPoints = new ArrayList<>(MAX_RESULT_POINTS);
+    }
+
+    public void setCameraPreview(CameraPreview view) {
+        this.cameraPreview = view;
+        view.addStateListener(new CameraPreview.StateListener() {
+            @Override
+            public void previewSized() {
+                refreshSizes();
+                invalidate();
+            }
+
+            @Override
+            public void previewStarted() {
+
+            }
+
+            @Override
+            public void previewStopped() {
+
+            }
+
+            @Override
+            public void cameraError(Exception error) {
+
+            }
+
+            @Override
+            public void cameraClosed() {
+
+            }
+        });
+    }
+
+    protected void refreshSizes() {
+        if (cameraPreview == null) {
+            return;
+        }
+        Rect framingRect = cameraPreview.getFramingRect();
+        Size previewSize = cameraPreview.getPreviewSize();
+        if (framingRect != null && previewSize != null) {
+            this.framingRect = framingRect;
+            this.previewSize = previewSize;
+        }
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        refreshSizes();
+        if (framingRect == null || previewSize == null) {
+            return;
+        }
+
+        final Rect frame = framingRect;
+        final Size previewSize = this.previewSize;
+
+        final int width = getWidth();
+        final int height = getHeight();
+
+        // Draw the exterior (i.e. outside the framing rect) darkened
+        paint.setColor(resultBitmap != null ? resultColor : maskColor);
+        canvas.drawRect(0, 0, width, frame.top, paint);
+        canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
+        canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);
+        canvas.drawRect(0, frame.bottom + 1, width, height, paint);
+
+        if (resultBitmap != null) {
+            // Draw the opaque result bitmap over the scanning rectangle
+            paint.setAlpha(CURRENT_POINT_OPACITY);
+            canvas.drawBitmap(resultBitmap, null, frame, paint);
+        } else {
+            // If wanted, draw a red "laser scanner" line through the middle to show decoding is active
+            if (laserVisibility) {
+                paint.setColor(laserColor);
+
+                paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
+                scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
+
+                final int middle = frame.height() / 2 + frame.top;
+                canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, middle + 2, paint);
+            }
+
+            final float scaleX = this.getWidth() / (float) previewSize.width;
+            final float scaleY = this.getHeight() / (float) previewSize.height;
+
+            // draw the last possible result points
+            if (!lastPossibleResultPoints.isEmpty()) {
+                paint.setAlpha(CURRENT_POINT_OPACITY / 2);
+                paint.setColor(resultPointColor);
+                float radius = POINT_SIZE / 2.0f;
+                for (final ResultPoint point : lastPossibleResultPoints) {
+                    canvas.drawCircle(
+                             (int) (point.getX() * scaleX),
+                             (int) (point.getY() * scaleY),
+                            radius, paint
+                    );
+                }
+                lastPossibleResultPoints.clear();
+            }
+
+            // draw current possible result points
+            if (!possibleResultPoints.isEmpty()) {
+                paint.setAlpha(CURRENT_POINT_OPACITY);
+                paint.setColor(resultPointColor);
+                for (final ResultPoint point : possibleResultPoints) {
+                    canvas.drawCircle(
+                            (int) (point.getX() * scaleX),
+                            (int) (point.getY() * scaleY),
+                            POINT_SIZE, paint
+                    );
+                }
+
+                // swap and clear buffers
+                final List<ResultPoint> temp = possibleResultPoints;
+                possibleResultPoints = lastPossibleResultPoints;
+                lastPossibleResultPoints = temp;
+                possibleResultPoints.clear();
+            }
+
+            // Request another update at the animation interval, but only repaint the laser line,
+            // not the entire viewfinder mask.
+            postInvalidateDelayed(ANIMATION_DELAY,
+                    frame.left - POINT_SIZE,
+                    frame.top - POINT_SIZE,
+                    frame.right + POINT_SIZE,
+                    frame.bottom + POINT_SIZE);
+        }
+    }
+
+    public void drawViewfinder() {
+        Bitmap resultBitmap = this.resultBitmap;
+        this.resultBitmap = null;
+        if (resultBitmap != null) {
+            resultBitmap.recycle();
+        }
+        invalidate();
+    }
+
+    /**
+     * Draw a bitmap with the result points highlighted instead of the live scanning display.
+     *
+     * @param result An image of the result.
+     */
+    public void drawResultBitmap(Bitmap result) {
+        resultBitmap = result;
+        invalidate();
+    }
+
+    /**
+     * Only call from the UI thread.
+     *
+     * @param point a point to draw, relative to the preview frame
+     */
+    public void addPossibleResultPoint(ResultPoint point) {
+        if (possibleResultPoints.size() < MAX_RESULT_POINTS)
+            possibleResultPoints.add(point);
+    }
+
+    public void setMaskColor(int maskColor) {
+        this.maskColor = maskColor;
+    }
+
+    public void setLaserVisibility(boolean visible) {
+        this.laserVisibility = visible;
+    }
+}

+ 134 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/AutoFocusManager.java

@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2012 ZXing authors
+ *
+ * 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.journeyapps.barcodescanner.camera;
+
+import android.hardware.Camera;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * This should be created and used from the camera thread only. The thread message queue is used
+ * to run all operations on the same thread.
+ */
+public final class AutoFocusManager {
+
+    private static final String TAG = AutoFocusManager.class.getSimpleName();
+
+    private static final long AUTO_FOCUS_INTERVAL_MS = 2000L;
+
+    private boolean stopped;
+    private boolean focusing;
+    private final boolean useAutoFocus;
+    private final Camera camera;
+    private Handler handler;
+
+    private int MESSAGE_FOCUS = 1;
+
+    private static final Collection<String> FOCUS_MODES_CALLING_AF;
+
+    static {
+        FOCUS_MODES_CALLING_AF = new ArrayList<>(2);
+        FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_AUTO);
+        FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_MACRO);
+    }
+
+    private final Handler.Callback focusHandlerCallback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message msg) {
+            if (msg.what == MESSAGE_FOCUS) {
+                focus();
+                return true;
+            }
+            return false;
+        }
+    };
+
+    private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
+        @Override
+        public void onAutoFocus(boolean success, Camera theCamera) {
+            handler.post(() -> {
+                focusing = false;
+                autoFocusAgainLater();
+            });
+        }
+    };
+
+    public AutoFocusManager(Camera camera, CameraSettings settings) {
+        this.handler = new Handler(focusHandlerCallback);
+        this.camera = camera;
+        String currentFocusMode = camera.getParameters().getFocusMode();
+        useAutoFocus = settings.isAutoFocusEnabled() && FOCUS_MODES_CALLING_AF.contains(currentFocusMode);
+        Log.i(TAG, "Current focus mode '" + currentFocusMode + "'; use auto focus? " + useAutoFocus);
+        start();
+    }
+
+    private synchronized void autoFocusAgainLater() {
+        if (!stopped && !handler.hasMessages(MESSAGE_FOCUS)) {
+            handler.sendMessageDelayed(handler.obtainMessage(MESSAGE_FOCUS), AUTO_FOCUS_INTERVAL_MS);
+        }
+    }
+
+    /**
+     * Start auto-focus. The first focus will happen now, then repeated every two seconds.
+     */
+    public void start() {
+        stopped = false;
+        focus();
+    }
+
+    private void focus() {
+        if (useAutoFocus) {
+            if (!stopped && !focusing) {
+                try {
+                    camera.autoFocus(autoFocusCallback);
+                    focusing = true;
+                } catch (RuntimeException re) {
+                    // Have heard RuntimeException reported in Android 4.0.x+; continue?
+                    Log.w(TAG, "Unexpected exception while focusing", re);
+                    // Try again later to keep cycle going
+                    autoFocusAgainLater();
+                }
+            }
+        }
+    }
+
+    private void cancelOutstandingTask() {
+        handler.removeMessages(MESSAGE_FOCUS);
+    }
+
+    /**
+     * Stop auto-focus.
+     */
+    public void stop() {
+        stopped = true;
+        focusing = false;
+        cancelOutstandingTask();
+        if (useAutoFocus) {
+            // Doesn't hurt to call this even if not focusing
+            try {
+                camera.cancelAutoFocus();
+            } catch (RuntimeException re) {
+                // Have heard RuntimeException reported in Android 4.0.x+; continue?
+                Log.w(TAG, "Unexpected exception while cancelling focusing", re);
+            }
+        }
+    }
+}

+ 366 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraConfigurationUtils.java

@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2014 ZXing authors
+ *
+ * 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.journeyapps.barcodescanner.camera;
+
+import android.annotation.TargetApi;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.os.Build;
+import android.util.Log;
+
+import com.journeyapps.barcodescanner.camera.CameraSettings;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Utility methods for configuring the Android camera.
+ *
+ * @author Sean Owen
+ */
+public final class CameraConfigurationUtils {
+
+    private static final String TAG = "CameraConfiguration";
+
+    private static final Pattern SEMICOLON = Pattern.compile(";");
+
+    private static final float MAX_EXPOSURE_COMPENSATION = 1.5f;
+    private static final float MIN_EXPOSURE_COMPENSATION = 0.0f;
+    private static final int MIN_FPS = 10;
+    private static final int MAX_FPS = 20;
+    private static final int AREA_PER_1000 = 400;
+
+    private CameraConfigurationUtils() {
+    }
+
+    public static void setFocus(Camera.Parameters parameters,
+								CameraSettings.FocusMode focusModeSetting,
+                                boolean safeMode) {
+        List<String> supportedFocusModes = parameters.getSupportedFocusModes();
+        String focusMode = null;
+
+		if (safeMode || focusModeSetting == CameraSettings.FocusMode.AUTO) {
+			focusMode = findSettableValue("focus mode",
+					supportedFocusModes,
+					Camera.Parameters.FOCUS_MODE_AUTO);
+		} else if (focusModeSetting == CameraSettings.FocusMode.CONTINUOUS) {
+			focusMode = findSettableValue("focus mode",
+					supportedFocusModes,
+					Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
+					Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO,
+					Camera.Parameters.FOCUS_MODE_AUTO);
+		} else if (focusModeSetting == CameraSettings.FocusMode.INFINITY) {
+			focusMode = findSettableValue("focus mode",
+					supportedFocusModes,
+					Camera.Parameters.FOCUS_MODE_INFINITY);
+		} else if (focusModeSetting == CameraSettings.FocusMode.MACRO) {
+			focusMode = findSettableValue("focus mode",
+					supportedFocusModes,
+					Camera.Parameters.FOCUS_MODE_MACRO);
+		}
+
+        // Maybe selected auto-focus but not available, so fall through here:
+        if (!safeMode && focusMode == null) {
+            focusMode = findSettableValue("focus mode",
+                    supportedFocusModes,
+                    Camera.Parameters.FOCUS_MODE_MACRO,
+                    Camera.Parameters.FOCUS_MODE_EDOF);
+        }
+        if (focusMode != null) {
+            if (focusMode.equals(parameters.getFocusMode())) {
+                Log.i(TAG, "Focus mode already set to " + focusMode);
+            } else {
+                parameters.setFocusMode(focusMode);
+            }
+        }
+    }
+
+    public static void setTorch(Camera.Parameters parameters, boolean on) {
+        List<String> supportedFlashModes = parameters.getSupportedFlashModes();
+        String flashMode;
+        if (on) {
+            flashMode = findSettableValue("flash mode",
+                    supportedFlashModes,
+                    Camera.Parameters.FLASH_MODE_TORCH,
+                    Camera.Parameters.FLASH_MODE_ON);
+        } else {
+            flashMode = findSettableValue("flash mode",
+                    supportedFlashModes,
+                    Camera.Parameters.FLASH_MODE_OFF);
+        }
+        if (flashMode != null) {
+            if (flashMode.equals(parameters.getFlashMode())) {
+                Log.i(TAG, "Flash mode already set to " + flashMode);
+            } else {
+                Log.i(TAG, "Setting flash mode to " + flashMode);
+                parameters.setFlashMode(flashMode);
+            }
+        }
+    }
+
+    public static void setBestExposure(Camera.Parameters parameters, boolean lightOn) {
+        int minExposure = parameters.getMinExposureCompensation();
+        int maxExposure = parameters.getMaxExposureCompensation();
+        float step = parameters.getExposureCompensationStep();
+        if ((minExposure != 0 || maxExposure != 0) && step > 0.0f) {
+            // Set low when light is on
+            float targetCompensation = lightOn ? MIN_EXPOSURE_COMPENSATION : MAX_EXPOSURE_COMPENSATION;
+            int compensationSteps = Math.round(targetCompensation / step);
+            float actualCompensation = step * compensationSteps;
+            // Clamp value:
+            compensationSteps = Math.max(Math.min(compensationSteps, maxExposure), minExposure);
+            if (parameters.getExposureCompensation() == compensationSteps) {
+                Log.i(TAG, "Exposure compensation already set to " + compensationSteps + " / " + actualCompensation);
+            } else {
+                Log.i(TAG, "Setting exposure compensation to " + compensationSteps + " / " + actualCompensation);
+                parameters.setExposureCompensation(compensationSteps);
+            }
+        } else {
+            Log.i(TAG, "Camera does not support exposure compensation");
+        }
+    }
+
+    public static void setBestPreviewFPS(Camera.Parameters parameters) {
+        setBestPreviewFPS(parameters, MIN_FPS, MAX_FPS);
+    }
+
+    public static void setBestPreviewFPS(Camera.Parameters parameters, int minFPS, int maxFPS) {
+        List<int[]> supportedPreviewFpsRanges = parameters.getSupportedPreviewFpsRange();
+        Log.i(TAG, "Supported FPS ranges: " + toString(supportedPreviewFpsRanges));
+        if (supportedPreviewFpsRanges != null && !supportedPreviewFpsRanges.isEmpty()) {
+            int[] suitableFPSRange = null;
+            for (int[] fpsRange : supportedPreviewFpsRanges) {
+                int thisMin = fpsRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX];
+                int thisMax = fpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX];
+                if (thisMin >= minFPS * 1000 && thisMax <= maxFPS * 1000) {
+                    suitableFPSRange = fpsRange;
+                    break;
+                }
+            }
+            if (suitableFPSRange == null) {
+                Log.i(TAG, "No suitable FPS range?");
+            } else {
+                int[] currentFpsRange = new int[2];
+                parameters.getPreviewFpsRange(currentFpsRange);
+                if (Arrays.equals(currentFpsRange, suitableFPSRange)) {
+                    Log.i(TAG, "FPS range already set to " + Arrays.toString(suitableFPSRange));
+                } else {
+                    Log.i(TAG, "Setting FPS range to " + Arrays.toString(suitableFPSRange));
+                    parameters.setPreviewFpsRange(suitableFPSRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+                            suitableFPSRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+                }
+            }
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+    public static void setFocusArea(Camera.Parameters parameters) {
+        if (parameters.getMaxNumFocusAreas() > 0) {
+            Log.i(TAG, "Old focus areas: " + toString(parameters.getFocusAreas()));
+            List<Camera.Area> middleArea = buildMiddleArea(AREA_PER_1000);
+            Log.i(TAG, "Setting focus area to : " + toString(middleArea));
+            parameters.setFocusAreas(middleArea);
+        } else {
+            Log.i(TAG, "Device does not support focus areas");
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+    public static void setMetering(Camera.Parameters parameters) {
+        if (parameters.getMaxNumMeteringAreas() > 0) {
+            Log.i(TAG, "Old metering areas: " + parameters.getMeteringAreas());
+            List<Camera.Area> middleArea = buildMiddleArea(AREA_PER_1000);
+            Log.i(TAG, "Setting metering area to : " + toString(middleArea));
+            parameters.setMeteringAreas(middleArea);
+        } else {
+            Log.i(TAG, "Device does not support metering areas");
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+    private static List<Camera.Area> buildMiddleArea(int areaPer1000) {
+        return Collections.singletonList(
+                new Camera.Area(new Rect(-areaPer1000, -areaPer1000, areaPer1000, areaPer1000), 1));
+    }
+
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+    public static void setVideoStabilization(Camera.Parameters parameters) {
+        if (parameters.isVideoStabilizationSupported()) {
+            if (parameters.getVideoStabilization()) {
+                Log.i(TAG, "Video stabilization already enabled");
+            } else {
+                Log.i(TAG, "Enabling video stabilization...");
+                parameters.setVideoStabilization(true);
+            }
+        } else {
+            Log.i(TAG, "This device does not support video stabilization");
+        }
+    }
+
+    public static void setBarcodeSceneMode(Camera.Parameters parameters) {
+        if (Camera.Parameters.SCENE_MODE_BARCODE.equals(parameters.getSceneMode())) {
+            Log.i(TAG, "Barcode scene mode already set");
+            return;
+        }
+        String sceneMode = findSettableValue("scene mode",
+                parameters.getSupportedSceneModes(),
+                Camera.Parameters.SCENE_MODE_BARCODE);
+        if (sceneMode != null) {
+            parameters.setSceneMode(sceneMode);
+        }
+    }
+
+    public static void setZoom(Camera.Parameters parameters, double targetZoomRatio) {
+        if (parameters.isZoomSupported()) {
+            Integer zoom = indexOfClosestZoom(parameters, targetZoomRatio);
+            if (zoom == null) {
+                return;
+            }
+            if (parameters.getZoom() == zoom) {
+                Log.i(TAG, "Zoom is already set to " + zoom);
+            } else {
+                Log.i(TAG, "Setting zoom to " + zoom);
+                parameters.setZoom(zoom);
+            }
+        } else {
+            Log.i(TAG, "Zoom is not supported");
+        }
+    }
+
+    private static Integer indexOfClosestZoom(Camera.Parameters parameters, double targetZoomRatio) {
+        List<Integer> ratios = parameters.getZoomRatios();
+        Log.i(TAG, "Zoom ratios: " + ratios);
+        int maxZoom = parameters.getMaxZoom();
+        if (ratios == null || ratios.isEmpty() || ratios.size() != maxZoom + 1) {
+            Log.w(TAG, "Invalid zoom ratios!");
+            return null;
+        }
+        double target100 = 100.0 * targetZoomRatio;
+        double smallestDiff = Double.POSITIVE_INFINITY;
+        int closestIndex = 0;
+        for (int i = 0; i < ratios.size(); i++) {
+            double diff = Math.abs(ratios.get(i) - target100);
+            if (diff < smallestDiff) {
+                smallestDiff = diff;
+                closestIndex = i;
+            }
+        }
+        Log.i(TAG, "Chose zoom ratio of " + (ratios.get(closestIndex) / 100.0));
+        return closestIndex;
+    }
+
+    public static void setInvertColor(Camera.Parameters parameters) {
+        if (Camera.Parameters.EFFECT_NEGATIVE.equals(parameters.getColorEffect())) {
+            Log.i(TAG, "Negative effect already set");
+            return;
+        }
+        String colorMode = findSettableValue("color effect",
+                parameters.getSupportedColorEffects(),
+                Camera.Parameters.EFFECT_NEGATIVE);
+        if (colorMode != null) {
+            parameters.setColorEffect(colorMode);
+        }
+    }
+
+    private static String findSettableValue(String name,
+                                            Collection<String> supportedValues,
+                                            String... desiredValues) {
+        Log.i(TAG, "Requesting " + name + " value from among: " + Arrays.toString(desiredValues));
+        Log.i(TAG, "Supported " + name + " values: " + supportedValues);
+        if (supportedValues != null) {
+            for (String desiredValue : desiredValues) {
+                if (supportedValues.contains(desiredValue)) {
+                    Log.i(TAG, "Can set " + name + " to: " + desiredValue);
+                    return desiredValue;
+                }
+            }
+        }
+        Log.i(TAG, "No supported values match");
+        return null;
+    }
+
+    private static String toString(Collection<int[]> arrays) {
+        if (arrays == null || arrays.isEmpty()) {
+            return "[]";
+        }
+        StringBuilder buffer = new StringBuilder();
+        buffer.append('[');
+        Iterator<int[]> it = arrays.iterator();
+        while (it.hasNext()) {
+            buffer.append(Arrays.toString(it.next()));
+            if (it.hasNext()) {
+                buffer.append(", ");
+            }
+        }
+        buffer.append(']');
+        return buffer.toString();
+    }
+
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+    private static String toString(Iterable<Camera.Area> areas) {
+        if (areas == null) {
+            return null;
+        }
+        StringBuilder result = new StringBuilder();
+        for (Camera.Area area : areas) {
+            result.append(area.rect).append(':').append(area.weight).append(' ');
+        }
+        return result.toString();
+    }
+
+    public static String collectStats(Camera.Parameters parameters) {
+        return collectStats(parameters.flatten());
+    }
+
+    public static String collectStats(CharSequence flattenedParams) {
+        StringBuilder result = new StringBuilder(1000);
+
+        result.append("BOARD=").append(Build.BOARD).append('\n');
+        result.append("BRAND=").append(Build.BRAND).append('\n');
+        result.append("CPU_ABI=").append(Build.CPU_ABI).append('\n');
+        result.append("DEVICE=").append(Build.DEVICE).append('\n');
+        result.append("DISPLAY=").append(Build.DISPLAY).append('\n');
+        result.append("FINGERPRINT=").append(Build.FINGERPRINT).append('\n');
+        result.append("HOST=").append(Build.HOST).append('\n');
+        result.append("ID=").append(Build.ID).append('\n');
+        result.append("MANUFACTURER=").append(Build.MANUFACTURER).append('\n');
+        result.append("MODEL=").append(Build.MODEL).append('\n');
+        result.append("PRODUCT=").append(Build.PRODUCT).append('\n');
+        result.append("TAGS=").append(Build.TAGS).append('\n');
+        result.append("TIME=").append(Build.TIME).append('\n');
+        result.append("TYPE=").append(Build.TYPE).append('\n');
+        result.append("USER=").append(Build.USER).append('\n');
+        result.append("VERSION.CODENAME=").append(Build.VERSION.CODENAME).append('\n');
+        result.append("VERSION.INCREMENTAL=").append(Build.VERSION.INCREMENTAL).append('\n');
+        result.append("VERSION.RELEASE=").append(Build.VERSION.RELEASE).append('\n');
+        result.append("VERSION.SDK_INT=").append(Build.VERSION.SDK_INT).append('\n');
+
+        if (flattenedParams != null) {
+            String[] params = SEMICOLON.split(flattenedParams);
+            Arrays.sort(params);
+            for (String param : params) {
+                result.append(param).append('\n');
+            }
+        }
+
+        return result.toString();
+    }
+}

+ 289 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraInstance.java

@@ -0,0 +1,289 @@
+package com.journeyapps.barcodescanner.camera;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import com.journeyapps.barcodescanner.R;
+import com.journeyapps.barcodescanner.Size;
+import com.journeyapps.barcodescanner.Util;
+
+/**
+ * Manage a camera instance using a background thread.
+ *
+ * All methods must be called from the main thread.
+ */
+public class CameraInstance {
+    private static final String TAG = CameraInstance.class.getSimpleName();
+
+    private CameraThread cameraThread;
+    private CameraSurface surface;
+
+    private CameraManager cameraManager;
+    private Handler readyHandler;
+    private DisplayConfiguration displayConfiguration;
+    private boolean open = false;
+    private boolean cameraClosed = true;
+    private Handler mainHandler;
+
+    private CameraSettings cameraSettings = new CameraSettings();
+
+    /**
+     * Construct a new CameraInstance.
+     *
+     * A new CameraManager is created.
+     *
+     * @param context the Android Context
+     */
+    public CameraInstance(Context context) {
+        Util.validateMainThread();
+
+        this.cameraThread = CameraThread.getInstance();
+        this.cameraManager = new CameraManager(context);
+        this.cameraManager.setCameraSettings(cameraSettings);
+        this.mainHandler = new Handler();
+    }
+
+    /**
+     * Construct a new CameraInstance with a specific CameraManager.
+     *
+     * @param cameraManager the CameraManager to use
+     */
+    public CameraInstance(CameraManager cameraManager) {
+        Util.validateMainThread();
+
+        this.cameraManager = cameraManager;
+    }
+
+    public void setDisplayConfiguration(DisplayConfiguration configuration) {
+        this.displayConfiguration = configuration;
+        cameraManager.setDisplayConfiguration(configuration);
+    }
+
+    public DisplayConfiguration getDisplayConfiguration() {
+        return displayConfiguration;
+    }
+
+    public void setReadyHandler(Handler readyHandler) {
+        this.readyHandler = readyHandler;
+    }
+
+    public void setSurfaceHolder(SurfaceHolder surfaceHolder) {
+        setSurface(new CameraSurface(surfaceHolder));
+    }
+
+    public void setSurface(CameraSurface surface) {
+        this.surface = surface;
+    }
+
+    public CameraSettings getCameraSettings() {
+        return cameraSettings;
+    }
+
+    /**
+     * This only has an effect if the camera is not opened yet.
+     *
+     * @param cameraSettings the new camera settings
+     */
+    public void setCameraSettings(CameraSettings cameraSettings) {
+        if (!open) {
+            this.cameraSettings = cameraSettings;
+            this.cameraManager.setCameraSettings(cameraSettings);
+        }
+    }
+
+    /**
+     * Actual preview size in current rotation. null if not determined yet.
+     *
+     * @return preview size
+     */
+    private Size getPreviewSize() {
+        return cameraManager.getPreviewSize();
+    }
+
+    /**
+     *
+     * @return the camera rotation relative to display rotation, in degrees. Typically 0 if the
+     *    display is in landscape orientation.
+     */
+    public int getCameraRotation() {
+        return cameraManager.getCameraRotation();
+    }
+
+    public void open() {
+        Util.validateMainThread();
+
+        open = true;
+        cameraClosed = false;
+
+        cameraThread.incrementAndEnqueue(opener);
+    }
+
+    public void configureCamera() {
+        Util.validateMainThread();
+        validateOpen();
+
+        cameraThread.enqueue(configure);
+    }
+
+    public void startPreview() {
+        Util.validateMainThread();
+        validateOpen();
+
+        cameraThread.enqueue(previewStarter);
+    }
+
+    public void setTorch(final boolean on) {
+        Util.validateMainThread();
+
+        if (open) {
+            cameraThread.enqueue(() -> cameraManager.setTorch(on));
+        }
+    }
+
+    /**
+     * Changes the settings for Camera.
+     *
+     * @param callback {@link CameraParametersCallback}
+     */
+    public void changeCameraParameters(final CameraParametersCallback callback) {
+        Util.validateMainThread();
+
+        if (open) {
+            cameraThread.enqueue(() -> cameraManager.changeCameraParameters(callback));
+        }
+    }
+
+    public void close() {
+        Util.validateMainThread();
+
+        if (open) {
+            cameraThread.enqueue(closer);
+        } else {
+            cameraClosed = true;
+        }
+
+        open = false;
+    }
+
+    public boolean isOpen() {
+        return open;
+    }
+
+    public boolean isCameraClosed() {
+        return cameraClosed;
+    }
+
+    public void requestPreview(final PreviewCallback callback) {
+        mainHandler.post(() -> {
+            if (!open) {
+                Log.d(TAG, "Camera is closed, not requesting preview");
+                return;
+            }
+
+            cameraThread.enqueue(() -> cameraManager.requestPreviewFrame(callback));
+        });
+    }
+
+    private void validateOpen() {
+        if (!open) {
+            throw new IllegalStateException("CameraInstance is not open");
+        }
+    }
+
+    private Runnable opener = new Runnable() {
+        @Override
+        public void run() {
+            try {
+                Log.d(TAG, "Opening camera");
+                cameraManager.open();
+            } catch (Exception e) {
+                notifyError(e);
+                Log.e(TAG, "Failed to open camera", e);
+            }
+        }
+    };
+
+    private Runnable configure = new Runnable() {
+        @Override
+        public void run() {
+            try {
+                Log.d(TAG, "Configuring camera");
+                cameraManager.configure();
+                if (readyHandler != null) {
+                    readyHandler.obtainMessage(R.id.zxing_prewiew_size_ready, getPreviewSize()).sendToTarget();
+                }
+            } catch (Exception e) {
+                notifyError(e);
+                Log.e(TAG, "Failed to configure camera", e);
+            }
+        }
+    };
+
+    private Runnable previewStarter = new Runnable() {
+        @Override
+        public void run() {
+            try {
+                Log.d(TAG, "Starting preview");
+                cameraManager.setPreviewDisplay(surface);
+                cameraManager.startPreview();
+            } catch (Exception e) {
+                notifyError(e);
+                Log.e(TAG, "Failed to start preview", e);
+            }
+        }
+    };
+
+    private Runnable closer = new Runnable() {
+        @Override
+        public void run() {
+            try {
+                Log.d(TAG, "Closing camera");
+                cameraManager.stopPreview();
+                cameraManager.close();
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to close camera", e);
+            }
+
+            cameraClosed = true;
+
+            readyHandler.sendEmptyMessage(R.id.zxing_camera_closed);
+
+            cameraThread.decrementInstances();
+        }
+    };
+
+    private void notifyError(Exception error) {
+        if (readyHandler != null) {
+            readyHandler.obtainMessage(R.id.zxing_camera_error, error).sendToTarget();
+        }
+    }
+
+    /**
+     * Returns the CameraManager used to control the camera.
+     *
+     * The CameraManager is not thread-safe, and must only be used from the CameraThread.
+     *
+     * @return the CameraManager used
+     */
+    protected CameraManager getCameraManager() {
+        return cameraManager;
+    }
+
+    /**
+     *
+     * @return the CameraThread used to manage the camera
+     */
+    protected CameraThread getCameraThread() {
+        return cameraThread;
+    }
+
+    /**
+     *
+     * @return the surface om which the preview is displayed
+     */
+    protected CameraSurface getSurface() {
+        return surface;
+    }
+}

+ 522 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraManager.java

@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2008 ZXing authors
+ *
+ * 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.journeyapps.barcodescanner.camera;
+
+import android.content.Context;
+import android.hardware.Camera;
+import android.os.Build;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import com.google.zxing.client.android.AmbientLightManager;
+import com.google.zxing.client.android.camera.open.OpenCameraInterface;
+import com.journeyapps.barcodescanner.Size;
+import com.journeyapps.barcodescanner.SourceData;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Wrapper to manage the Camera. This is not thread-safe, and the methods must always be called
+ * from the same thread.
+ *
+ *
+ * Call order:
+ *
+ * 1. setCameraSettings()
+ * 2. open(), set desired preview size (any order)
+ * 3. configure(), setPreviewDisplay(holder) (any order)
+ * 4. startPreview()
+ * 5. requestPreviewFrame (repeat)
+ * 6. stopPreview()
+ * 7. close()
+ */
+public final class CameraManager {
+
+    private static final String TAG = CameraManager.class.getSimpleName();
+
+    private Camera camera;
+    private Camera.CameraInfo cameraInfo;
+
+    private AutoFocusManager autoFocusManager;
+    private AmbientLightManager ambientLightManager;
+
+    private boolean previewing;
+    private String defaultParameters;
+
+    // User parameters
+    private CameraSettings settings = new CameraSettings();
+
+    private DisplayConfiguration displayConfiguration;
+
+    // Actual chosen preview size
+    private Size requestedPreviewSize;
+    private Size previewSize;
+
+    private int rotationDegrees = -1;    // camera rotation vs display rotation
+
+    private Context context;
+
+
+    private final class CameraPreviewCallback implements Camera.PreviewCallback {
+        private PreviewCallback callback;
+
+        private Size resolution;
+
+        public CameraPreviewCallback() {
+        }
+
+        public void setResolution(Size resolution) {
+            this.resolution = resolution;
+        }
+
+        public void setCallback(PreviewCallback callback) {
+            this.callback = callback;
+        }
+
+        @Override
+        public void onPreviewFrame(byte[] data, Camera camera) {
+            Size cameraResolution = resolution;
+            PreviewCallback callback = this.callback;
+            if (cameraResolution != null && callback != null) {
+                try {
+                    if (data == null) {
+                        throw new NullPointerException("No preview data received");
+                    }
+                    int format = camera.getParameters().getPreviewFormat();
+                    SourceData source = new SourceData(data, cameraResolution.width, cameraResolution.height, format, getCameraRotation());
+
+                    if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+                        source.setPreviewMirrored(true);
+                    }
+                    callback.onPreview(source);
+                } catch (RuntimeException e) {
+                    // Could be:
+                    // java.lang.RuntimeException: getParameters failed (empty parameters)
+                    // IllegalArgumentException: Image data does not match the resolution
+                    Log.e(TAG, "Camera preview failed", e);
+                    callback.onPreviewError(e);
+                }
+            } else {
+                Log.d(TAG, "Got preview callback, but no handler or resolution available");
+                if (callback != null) {
+                    // Should generally not happen
+                    callback.onPreviewError(new Exception("No resolution available"));
+                }
+            }
+        }
+    }
+
+    /**
+     * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
+     * clear the handler so it will only receive one message.
+     */
+    private final CameraPreviewCallback cameraPreviewCallback;
+
+    public CameraManager(Context context) {
+        this.context = context;
+        cameraPreviewCallback = new CameraPreviewCallback();
+    }
+
+    /**
+     * Must be called from camera thread.
+     */
+    public void open() {
+        camera = OpenCameraInterface.open(settings.getRequestedCameraId());
+        if (camera == null) {
+            throw new RuntimeException("Failed to open camera");
+        }
+
+        int cameraId = OpenCameraInterface.getCameraId(settings.getRequestedCameraId());
+        cameraInfo = new Camera.CameraInfo();
+        Camera.getCameraInfo(cameraId, cameraInfo);
+    }
+
+    /**
+     * Configure the camera parameters, including preview size.
+     *
+     * The camera must be opened before calling this.
+     *
+     * Must be called from camera thread.
+     */
+    public void configure() {
+        if (camera == null) {
+            throw new RuntimeException("Camera not open");
+        }
+        setParameters();
+    }
+
+    /**
+     * Must be called from camera thread.
+     */
+    public void setPreviewDisplay(SurfaceHolder holder) throws IOException {
+        setPreviewDisplay(new CameraSurface(holder));
+    }
+
+    public void setPreviewDisplay(CameraSurface surface) throws IOException {
+        surface.setPreview(camera);
+    }
+
+    /**
+     * Asks the camera hardware to begin drawing preview frames to the screen.
+     *
+     * Must be called from camera thread.
+     */
+    public void startPreview() {
+        Camera theCamera = camera;
+        if (theCamera != null && !previewing) {
+            theCamera.startPreview();
+            previewing = true;
+            autoFocusManager = new AutoFocusManager(camera, settings);
+            ambientLightManager = new AmbientLightManager(context, this, settings);
+            ambientLightManager.start();
+        }
+    }
+
+    /**
+     * Tells the camera to stop drawing preview frames.
+     *
+     * Must be called from camera thread.
+     */
+    public void stopPreview() {
+        if (autoFocusManager != null) {
+            autoFocusManager.stop();
+            autoFocusManager = null;
+        }
+        if (ambientLightManager != null) {
+            ambientLightManager.stop();
+            ambientLightManager = null;
+        }
+        if (camera != null && previewing) {
+            camera.stopPreview();
+            cameraPreviewCallback.setCallback(null);
+            previewing = false;
+        }
+    }
+
+    /**
+     * Closes the camera driver if still in use.
+     *
+     * Must be called from camera thread.
+     */
+    public void close() {
+        if (camera != null) {
+            camera.release();
+            camera = null;
+        }
+    }
+
+    /**
+     * @return true if the camera rotation is perpendicular to the current display rotation.
+     */
+    public boolean isCameraRotated() {
+        if (rotationDegrees == -1) {
+            throw new IllegalStateException("Rotation not calculated yet. Call configure() first.");
+        }
+        return rotationDegrees % 180 != 0;
+    }
+
+    /**
+     *
+     * @return the camera rotation relative to display rotation, in degrees. Typically 0 if the
+     *    display is in landscape orientation.
+     */
+    public int getCameraRotation() {
+        return rotationDegrees;
+    }
+
+    private Camera.Parameters getDefaultCameraParameters() {
+        Camera.Parameters parameters = camera.getParameters();
+        if (defaultParameters == null) {
+            defaultParameters = parameters.flatten();
+        } else {
+            parameters.unflatten(defaultParameters);
+        }
+        return parameters;
+    }
+
+    private void setDesiredParameters(boolean safeMode) {
+        Camera.Parameters parameters = getDefaultCameraParameters();
+
+        //noinspection ConstantConditions
+        if (parameters == null) {
+            Log.w(TAG, "Device error: no camera parameters are available. Proceeding without configuration.");
+            return;
+        }
+
+        Log.i(TAG, "Initial camera parameters: " + parameters.flatten());
+
+        if (safeMode) {
+            Log.w(TAG, "In camera config safe mode -- most settings will not be honored");
+        }
+
+        CameraConfigurationUtils.setFocus(parameters, settings.getFocusMode(), safeMode);
+
+        if (!safeMode) {
+            CameraConfigurationUtils.setTorch(parameters, false);
+
+            if (settings.isScanInverted()) {
+                CameraConfigurationUtils.setInvertColor(parameters);
+            }
+
+            if (settings.isBarcodeSceneModeEnabled()) {
+                CameraConfigurationUtils.setBarcodeSceneMode(parameters);
+            }
+
+            if (settings.isMeteringEnabled()) {
+                CameraConfigurationUtils.setVideoStabilization(parameters);
+                CameraConfigurationUtils.setFocusArea(parameters);
+                CameraConfigurationUtils.setMetering(parameters);
+            }
+
+        }
+
+        List<Size> previewSizes = getPreviewSizes(parameters);
+        if (previewSizes.size() == 0) {
+            requestedPreviewSize = null;
+        } else {
+            requestedPreviewSize = displayConfiguration.getBestPreviewSize(previewSizes, isCameraRotated());
+
+            parameters.setPreviewSize(requestedPreviewSize.width, requestedPreviewSize.height);
+        }
+
+        if (Build.DEVICE.equals("glass-1")) {
+            // We need to set the FPS on Google Glass devices, otherwise the preview is scrambled.
+            // FIXME - can/should we do this for other devices as well?
+            CameraConfigurationUtils.setBestPreviewFPS(parameters);
+        }
+
+        Log.i(TAG, "Final camera parameters: " + parameters.flatten());
+
+        camera.setParameters(parameters);
+    }
+
+    private static List<Size> getPreviewSizes(Camera.Parameters parameters) {
+        List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
+        List<Size> previewSizes = new ArrayList<>();
+        if (rawSupportedSizes == null) {
+            Camera.Size defaultSize = parameters.getPreviewSize();
+            if (defaultSize != null) {
+                Size previewSize = new Size(defaultSize.width, defaultSize.height);
+                previewSizes.add(new Size(defaultSize.width, defaultSize.height));
+            }
+            return previewSizes;
+        }
+        for (Camera.Size size : rawSupportedSizes) {
+            previewSizes.add(new Size(size.width, size.height));
+        }
+        return previewSizes;
+    }
+
+    private int calculateDisplayRotation() {
+        // http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)
+        int rotation = displayConfiguration.getRotation();
+        int degrees = 0;
+        switch (rotation) {
+            case Surface.ROTATION_0:
+                degrees = 0;
+                break;
+            case Surface.ROTATION_90:
+                degrees = 90;
+                break;
+            case Surface.ROTATION_180:
+                degrees = 180;
+                break;
+            case Surface.ROTATION_270:
+                degrees = 270;
+                break;
+        }
+
+        int result;
+        if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+            result = (cameraInfo.orientation + degrees) % 360;
+            result = (360 - result) % 360;  // compensate the mirror
+        } else {  // back-facing
+            result = (cameraInfo.orientation - degrees + 360) % 360;
+        }
+        Log.i(TAG, "Camera Display Orientation: " + result);
+        return result;
+    }
+
+    private void setCameraDisplayOrientation(int rotation) {
+        camera.setDisplayOrientation(rotation);
+    }
+
+    private void setParameters() {
+        try {
+            this.rotationDegrees = calculateDisplayRotation();
+            setCameraDisplayOrientation(rotationDegrees);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to set rotation.");
+        }
+        try {
+            setDesiredParameters(false);
+        } catch (Exception e) {
+            // Failed, use safe mode
+            try {
+                setDesiredParameters(true);
+            } catch (Exception e2) {
+                // Well, darn. Give up
+                Log.w(TAG, "Camera rejected even safe-mode parameters! No configuration");
+            }
+        }
+
+        Camera.Size realPreviewSize = camera.getParameters().getPreviewSize();
+        if (realPreviewSize == null) {
+            previewSize = requestedPreviewSize;
+        } else {
+            previewSize = new Size(realPreviewSize.width, realPreviewSize.height);
+        }
+        cameraPreviewCallback.setResolution(previewSize);
+    }
+
+    /**
+     * This returns false if the camera is not opened yet, failed to open, or has
+     * been closed.
+     */
+    public boolean isOpen() {
+        return camera != null;
+    }
+
+    /**
+     * Actual preview size in *natural camera* orientation. null if not determined yet.
+     *
+     * @return preview size
+     */
+    public Size getNaturalPreviewSize() {
+        return previewSize;
+    }
+
+    /**
+     * Actual preview size in *current display* rotation. null if not determined yet.
+     *
+     * @return preview size
+     */
+    public Size getPreviewSize() {
+        if (previewSize == null) {
+            return null;
+        } else if (this.isCameraRotated()) {
+            return previewSize.rotate();
+        } else {
+            return previewSize;
+        }
+    }
+
+    /**
+     * A single preview frame will be returned to the supplied callback.
+     *
+     * The thread on which this called is undefined, so a Handler should be used to post the result
+     * to the correct thread.
+     *
+     * @param callback The callback to receive the preview.
+     */
+    public void requestPreviewFrame(PreviewCallback callback) {
+        Camera theCamera = camera;
+        if (theCamera != null && previewing) {
+            cameraPreviewCallback.setCallback(callback);
+            theCamera.setOneShotPreviewCallback(cameraPreviewCallback);
+        }
+    }
+
+    public CameraSettings getCameraSettings() {
+        return settings;
+    }
+
+    public void setCameraSettings(CameraSettings settings) {
+        this.settings = settings;
+    }
+
+    public DisplayConfiguration getDisplayConfiguration() {
+        return displayConfiguration;
+    }
+
+    public void setDisplayConfiguration(DisplayConfiguration displayConfiguration) {
+        this.displayConfiguration = displayConfiguration;
+    }
+
+    public void setTorch(boolean on) {
+        if (camera != null) {
+            try {
+                boolean isOn = isTorchOn();
+                if (on != isOn) {
+                    if (autoFocusManager != null) {
+                        autoFocusManager.stop();
+                    }
+
+                    Camera.Parameters parameters = camera.getParameters();
+                    CameraConfigurationUtils.setTorch(parameters, on);
+                    if (settings.isExposureEnabled()) {
+                        CameraConfigurationUtils.setBestExposure(parameters, on);
+                    }
+                    camera.setParameters(parameters);
+
+                    if (autoFocusManager != null) {
+                        autoFocusManager.start();
+                    }
+                }
+            } catch(RuntimeException e) {
+                // Camera error. Could happen if the camera is being closed.
+                Log.e(TAG, "Failed to set torch", e);
+            }
+        }
+    }
+
+    /**
+     * Changes the settings for Camera.
+     *
+     * @param callback {@link CameraParametersCallback}
+     */
+    public void changeCameraParameters(CameraParametersCallback callback) {
+        if (camera != null) {
+            try {
+                camera.setParameters(callback.changeCameraParameters(camera.getParameters()));
+            } catch(RuntimeException e) {
+                // Camera error. Could happen if the camera is being closed.
+                Log.e(TAG, "Failed to change camera parameters", e);
+            }
+        }
+    }
+
+    /**
+     *
+     * @return true if the torch is on
+     * @throws RuntimeException if there is a camera error
+     */
+    public boolean isTorchOn() {
+        Camera.Parameters parameters = camera.getParameters();
+        if (parameters != null) {
+            String flashMode = parameters.getFlashMode();
+            return flashMode != null &&
+                    (Camera.Parameters.FLASH_MODE_ON.equals(flashMode) ||
+                            Camera.Parameters.FLASH_MODE_TORCH.equals(flashMode));
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Returns the Camera. This returns null if the camera is not opened yet, failed to open, or has
+     * been closed.
+     *
+     * @return the Camera
+     */
+    public Camera getCamera() {
+        return camera;
+    }
+}

+ 17 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraParametersCallback.java

@@ -0,0 +1,17 @@
+package com.journeyapps.barcodescanner.camera;
+
+import android.hardware.Camera;
+
+/**
+ * Callback for {@link Camera.Parameters}.
+ */
+public interface CameraParametersCallback {
+
+    /**
+     * Changes the settings for Camera.
+     *
+     * @param parameters {@link Camera.Parameters}.
+     * @return {@link Camera.Parameters} with arguments.
+     */
+    Camera.Parameters changeCameraParameters(Camera.Parameters parameters);
+}

+ 163 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraSettings.java

@@ -0,0 +1,163 @@
+package com.journeyapps.barcodescanner.camera;
+
+import com.google.zxing.client.android.camera.open.OpenCameraInterface;
+
+/**
+ *
+ */
+public class CameraSettings {
+    private int requestedCameraId = OpenCameraInterface.NO_REQUESTED_CAMERA;
+    private boolean scanInverted = false;
+    private boolean barcodeSceneModeEnabled = false;
+    private boolean meteringEnabled = false;
+    private boolean autoFocusEnabled = true;
+    private boolean continuousFocusEnabled = false;
+    private boolean exposureEnabled = false;
+    private boolean autoTorchEnabled = false;
+    private FocusMode focusMode = FocusMode.AUTO;
+
+    public enum FocusMode {
+        AUTO,
+        CONTINUOUS,
+        INFINITY,
+        MACRO
+    }
+
+    public int getRequestedCameraId() {
+        return requestedCameraId;
+    }
+
+    /**
+     * Allows third party apps to specify the camera ID, rather than determine
+     * it automatically based on available cameras and their orientation.
+     *
+     * @param requestedCameraId camera ID of the camera to use. A negative value means "no preference".
+     */
+    public void setRequestedCameraId(int requestedCameraId) {
+        this.requestedCameraId = requestedCameraId;
+    }
+
+    /**
+     * Default to false.
+     *
+     * Inverted means dark & light colors are inverted.
+     *
+     * @return true if scan is inverted
+     */
+    public boolean isScanInverted() {
+        return scanInverted;
+    }
+
+    public void setScanInverted(boolean scanInverted) {
+        this.scanInverted = scanInverted;
+    }
+
+    /**
+     * Default to false.
+     *
+     * @return true if barcode scene mode is enabled
+     */
+    public boolean isBarcodeSceneModeEnabled() {
+        return barcodeSceneModeEnabled;
+    }
+
+    public void setBarcodeSceneModeEnabled(boolean barcodeSceneModeEnabled) {
+        this.barcodeSceneModeEnabled = barcodeSceneModeEnabled;
+    }
+
+    /**
+     * Default to false.
+     *
+     * @return true if exposure is enabled.
+     */
+    public boolean isExposureEnabled() {
+        return exposureEnabled;
+    }
+
+    public void setExposureEnabled(boolean exposureEnabled) {
+        this.exposureEnabled = exposureEnabled;
+    }
+
+    /**
+     * Default to false.
+     *
+     * If enabled, metering is performed to determine focus area.
+     *
+     * @return true if metering is enabled
+     */
+    public boolean isMeteringEnabled() {
+        return meteringEnabled;
+    }
+
+    public void setMeteringEnabled(boolean meteringEnabled) {
+        this.meteringEnabled = meteringEnabled;
+    }
+
+    /**
+     * Default to true.
+     *
+     * @return true if auto-focus is enabled
+     */
+    public boolean isAutoFocusEnabled() {
+        return autoFocusEnabled;
+    }
+
+    public void setAutoFocusEnabled(boolean autoFocusEnabled) {
+        this.autoFocusEnabled = autoFocusEnabled;
+
+        if (autoFocusEnabled && continuousFocusEnabled) {
+            focusMode = FocusMode.CONTINUOUS;
+        } else if (autoFocusEnabled) {
+            focusMode = FocusMode.AUTO;
+        } else {
+            focusMode = null;
+        }
+    }
+
+    /**
+     * Default to false.
+     *
+     * @return true if continuous focus is enabled
+     */
+    public boolean isContinuousFocusEnabled() {
+        return continuousFocusEnabled;
+    }
+
+    public void setContinuousFocusEnabled(boolean continuousFocusEnabled) {
+        this.continuousFocusEnabled = continuousFocusEnabled;
+
+        if (continuousFocusEnabled) {
+            focusMode = FocusMode.CONTINUOUS;
+        } else if (autoFocusEnabled) {
+            focusMode = FocusMode.AUTO;
+        } else {
+            focusMode = null;
+        }
+    }
+
+    /**
+     * Default to FocusMode.AUTO.
+     *
+     * @return value of selected focus mode
+     */
+    public FocusMode getFocusMode() {
+        return focusMode;
+    }
+
+    public void setFocusMode(FocusMode focusMode) {
+        this.focusMode = focusMode;
+    }
+
+    /**
+     * Default to false.
+     *
+     * @return true if the torch is automatically controlled based on ambient light.
+     */
+    public boolean isAutoTorchEnabled() {
+        return autoTorchEnabled;
+    }
+
+    public void setAutoTorchEnabled(boolean autoTorchEnabled) {
+        this.autoTorchEnabled = autoTorchEnabled;
+    }
+}

+ 48 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraSurface.java

@@ -0,0 +1,48 @@
+package com.journeyapps.barcodescanner.camera;
+
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.os.Build;
+import android.view.SurfaceHolder;
+
+import java.io.IOException;
+
+/**
+ * A surface on which a camera preview is displayed.
+ *
+ * This wraps either a SurfaceHolder or a SurfaceTexture.
+ */
+public class CameraSurface {
+    private SurfaceHolder surfaceHolder;
+    private SurfaceTexture surfaceTexture;
+
+    public CameraSurface(SurfaceHolder surfaceHolder) {
+        if (surfaceHolder == null) {
+            throw new IllegalArgumentException("surfaceHolder may not be null");
+        }
+        this.surfaceHolder = surfaceHolder;
+    }
+
+    public CameraSurface(SurfaceTexture surfaceTexture) {
+        if (surfaceTexture == null) {
+            throw new IllegalArgumentException("surfaceTexture may not be null");
+        }
+        this.surfaceTexture = surfaceTexture;
+    }
+
+    public SurfaceHolder getSurfaceHolder() {
+        return surfaceHolder;
+    }
+
+    public SurfaceTexture getSurfaceTexture() {
+        return surfaceTexture;
+    }
+
+    public void setPreview(Camera camera) throws IOException {
+        if (surfaceHolder != null) {
+            camera.setPreviewDisplay(surfaceHolder);
+        } else {
+            camera.setPreviewTexture(surfaceTexture);
+        }
+    }
+}

+ 110 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CameraThread.java

@@ -0,0 +1,110 @@
+package com.journeyapps.barcodescanner.camera;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+
+/**
+ * Singleton thread that is started and stopped on demand.
+ *
+ * Any access to Camera / CameraManager should happen on this thread, through CameraInstance.
+ */
+class CameraThread {
+    private static final String TAG = CameraThread.class.getSimpleName();
+
+    private static CameraThread instance;
+
+    public static CameraThread getInstance() {
+        if (instance == null) {
+            instance = new CameraThread();
+        }
+        return instance;
+    }
+
+    private Handler handler;
+    private HandlerThread thread;
+
+    private int openCount = 0;
+
+    private final Object LOCK = new Object();
+
+
+    private CameraThread() {
+    }
+
+    /**
+     * Call from main thread or camera thread.
+     *
+     * Enqueues a task on the camera thread.
+     *
+     * @param runnable the task to enqueue
+     */
+    protected void enqueue(Runnable runnable) {
+        synchronized (LOCK) {
+            checkRunning();
+            this.handler.post(runnable);
+        }
+    }
+
+    /**
+     * Call from main thread or camera thread.
+     *
+     * Enqueues a task on the camera thread.
+     *
+     * @param runnable the task to enqueue
+     * @param delayMillis the delay in milliseconds before executing the runnable
+     */
+    protected void enqueueDelayed(Runnable runnable, long delayMillis) {
+        synchronized (LOCK) {
+            checkRunning();
+            this.handler.postDelayed(runnable, delayMillis);
+        }
+    }
+
+    private void checkRunning() {
+        synchronized (LOCK) {
+            if (this.handler == null) {
+                if (openCount <= 0) {
+                    throw new IllegalStateException("CameraThread is not open");
+                }
+                this.thread = new HandlerThread("CameraThread");
+                this.thread.start();
+                this.handler = new Handler(thread.getLooper());
+            }
+        }
+    }
+
+    /**
+     * Call from camera thread.
+     */
+    private void quit() {
+        synchronized (LOCK) {
+            this.thread.quit();
+            this.thread = null;
+            this.handler = null;
+        }
+    }
+
+    /**
+     * Call from camera thread
+     */
+    protected void decrementInstances() {
+        synchronized (LOCK) {
+            openCount -= 1;
+            if (openCount == 0) {
+                quit();
+            }
+        }
+    }
+
+    /**
+     * Call from main thread.
+     *
+     * @param runner The {@link Runnable} to be enqueued
+     */
+    protected void incrementAndEnqueue(Runnable runner) {
+        synchronized (LOCK) {
+            openCount += 1;
+            enqueue(runner);
+        }
+    }
+}

+ 78 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/CenterCropStrategy.java

@@ -0,0 +1,78 @@
+package com.journeyapps.barcodescanner.camera;
+
+import android.graphics.Rect;
+import android.util.Log;
+
+import com.journeyapps.barcodescanner.Size;
+
+/**
+ * Scales the dimensions so that it fits entirely inside the parent.One of width or height will
+ * fit exactly. Aspect ratio is preserved.
+ */
+public class CenterCropStrategy extends PreviewScalingStrategy {
+    private static final String TAG = CenterCropStrategy.class.getSimpleName();
+
+
+    /**
+     * Get a score for our size.
+     *
+     * Based on heuristics for penalizing scaling and cropping.
+     *
+     * 1.0 is perfect (exact match).
+     * 0.0 means we can't use it at all.
+     *
+     * @param size the camera preview size (that can be scaled)
+     * @param desired the viewfinder size
+     * @return the score
+     */
+    @Override
+    protected float getScore(Size size, Size desired) {
+        if (size.width <= 0 || size.height <= 0) {
+            return 0f;
+        }
+        Size scaled = size.scaleCrop(desired);
+        // Scaling preserves aspect ratio
+        float scaleRatio = scaled.width * 1.0f / size.width;
+
+        // Treat downscaling as slightly better than upscaling
+        float scaleScore;
+        if (scaleRatio > 1.0f) {
+            // Upscaling
+            scaleScore = (float)Math.pow(1.0f / scaleRatio, 1.1);
+        } else {
+            // Downscaling
+            scaleScore = scaleRatio;
+        }
+
+        // Ratio of scaledDimension / dimension.
+        // Note that with scaleCrop, only one dimension is cropped.
+        float cropRatio = scaled.width * 1.0f / desired.width +
+                scaled.height * 1.0f / desired.height;
+
+        // Cropping is bad, square it
+        // 1.0 means no cropping. 50% cropping is 0.44f, 10% cropping is 0.82f
+        float cropScore = 1.0f / cropRatio / cropRatio;
+
+        return scaleScore * cropScore;
+    }
+
+    /**
+     * Scale the preview to cover the viewfinder, then center it.
+     *
+     * Aspect ratio is preserved.
+     *
+     * @param previewSize the size of the preview (camera), in current display orientation
+     * @param viewfinderSize the size of the viewfinder (display), in current display orientation
+     * @return a rect placing the preview
+     */
+    public Rect scalePreview(Size previewSize, Size viewfinderSize) {
+        // We avoid scaling if feasible.
+        Size scaledPreview = previewSize.scaleCrop(viewfinderSize);
+        Log.i(TAG, "Preview: " + previewSize + "; Scaled: " + scaledPreview + "; Want: " + viewfinderSize);
+
+        int dx = (scaledPreview.width - viewfinderSize.width) / 2;
+        int dy = (scaledPreview.height - viewfinderSize.height) / 2;
+
+        return new Rect(-dx, -dy, scaledPreview.width - dx, scaledPreview.height - dy);
+    }
+}

+ 98 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/DisplayConfiguration.java

@@ -0,0 +1,98 @@
+package com.journeyapps.barcodescanner.camera;
+
+import android.graphics.Rect;
+
+import com.journeyapps.barcodescanner.Size;
+
+import java.util.List;
+
+/**
+ *
+ */
+public class DisplayConfiguration {
+    private static final String TAG = DisplayConfiguration.class.getSimpleName();
+
+    private Size viewfinderSize;
+    private int rotation;
+    private boolean center = false;
+    private PreviewScalingStrategy previewScalingStrategy = new FitCenterStrategy();
+
+    public DisplayConfiguration(int rotation) {
+        this.rotation = rotation;
+    }
+
+    public DisplayConfiguration(int rotation, Size viewfinderSize) {
+        this.rotation = rotation;
+        this.viewfinderSize = viewfinderSize;
+    }
+
+    public int getRotation() {
+        return rotation;
+    }
+
+    public Size getViewfinderSize() {
+        return viewfinderSize;
+    }
+
+    public PreviewScalingStrategy getPreviewScalingStrategy() {
+        return previewScalingStrategy;
+    }
+
+    public void setPreviewScalingStrategy(PreviewScalingStrategy previewScalingStrategy) {
+        this.previewScalingStrategy = previewScalingStrategy;
+    }
+
+    /**
+     * @param rotate true to rotate the preview size
+     * @return desired preview size in natural camera orientation.
+     */
+    public Size getDesiredPreviewSize(boolean rotate) {
+        if (viewfinderSize == null) {
+            return null;
+        } else if (rotate) {
+            return viewfinderSize.rotate();
+        } else {
+            return viewfinderSize;
+        }
+    }
+
+    /**
+     * Choose the best preview size, based on our display size.
+     *
+     * We prefer:
+     * 1. no scaling
+     * 2. least downscaling
+     * 3. least upscaling
+     *
+     * We do not care much about aspect ratio, since we just crop away extra pixels. We only choose
+     * the size to minimize scaling.
+     *
+     * In the future we may consider choosing the biggest possible preview size, to maximize the
+     * resolution we have for decoding. We need more testing to see whether or not that is feasible.
+     *
+     * @param sizes supported preview sizes, containing at least one size. Sizes are in natural camera orientation.
+     * @param isRotated true if the camera is rotated perpendicular to the current display orientation
+     * @return the best preview size, never null
+     */
+    public Size getBestPreviewSize(List<Size> sizes, boolean isRotated) {
+        // Sample of supported preview sizes:
+        // http://www.kirill.org/ar/ar.php
+
+
+        final Size desired = getDesiredPreviewSize(isRotated);
+
+        return previewScalingStrategy.getBestPreviewSize(sizes, desired);
+    }
+
+    /**
+     * Scale the preview to cover the viewfinder, then center it.
+     *
+     * Aspect ratio is preserved.
+     *
+     * @param previewSize the size of the preview (camera), in current display orientation
+     * @return a rect placing the preview
+     */
+    public Rect scalePreview(Size previewSize) {
+        return previewScalingStrategy.scalePreview(previewSize, viewfinderSize);
+    }
+}

+ 78 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/FitCenterStrategy.java

@@ -0,0 +1,78 @@
+package com.journeyapps.barcodescanner.camera;
+
+import android.graphics.Rect;
+import android.util.Log;
+
+import com.journeyapps.barcodescanner.Size;
+
+/**
+ * Scales the size so that both dimensions will be greater than or equal to the corresponding
+ * dimension of the parent. One of width or height will fit exactly. Aspect ratio is preserved.
+ */
+public class FitCenterStrategy extends PreviewScalingStrategy {
+    private static final String TAG = FitCenterStrategy.class.getSimpleName();
+
+
+    /**
+     * Get a score for our size.
+     *
+     * Based on heuristics for penalizing scaling and cropping.
+     *
+     * 1.0 is perfect (exact match).
+     * 0.0 means we can't use it at all.
+     *
+     * @param size the camera preview size (that can be scaled)
+     * @param desired the viewfinder size
+     * @return the score
+     */
+    @Override
+    protected float getScore(Size size, Size desired) {
+        if (size.width <= 0 || size.height <= 0) {
+            return 0f;
+        }
+        Size scaled = size.scaleFit(desired);
+        // Scaling preserves aspect ratio
+        float scaleRatio = scaled.width * 1.0f / size.width;
+
+        // Treat downscaling as slightly better than upscaling
+        float scaleScore;
+        if (scaleRatio > 1.0f) {
+            // Upscaling
+            scaleScore = (float)Math.pow(1.0f / scaleRatio, 1.1);
+        } else {
+            // Downscaling
+            scaleScore = scaleRatio;
+        }
+
+        // Ratio of scaledDimension / dimension.
+        // Note that with scaleCrop, only one dimension is cropped.
+        float cropRatio = (desired.width * 1.0f / scaled.width) *
+                (desired.height * 1.0f / scaled.height);
+
+        // Cropping is very bad, since it's used-visible for centerFit
+        // 1.0 means no cropping.
+        float cropScore = 1.0f / cropRatio / cropRatio / cropRatio;
+
+        return scaleScore * cropScore;
+    }
+
+    /**
+     * Scale the preview to cover the viewfinder, then center it.
+     *
+     * Aspect ratio is preserved.
+     *
+     * @param previewSize the size of the preview (camera), in current display orientation
+     * @param viewfinderSize the size of the viewfinder (display), in current display orientation
+     * @return a rect placing the preview
+     */
+    public Rect scalePreview(Size previewSize, Size viewfinderSize) {
+        // We avoid scaling if feasible.
+        Size scaledPreview = previewSize.scaleFit(viewfinderSize);
+        Log.i(TAG, "Preview: " + previewSize + "; Scaled: " + scaledPreview + "; Want: " + viewfinderSize);
+
+        int dx = (scaledPreview.width - viewfinderSize.width) / 2;
+        int dy = (scaledPreview.height - viewfinderSize.height) / 2;
+
+        return new Rect(-dx, -dy, scaledPreview.width - dx, scaledPreview.height - dy);
+    }
+}

+ 62 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/FitXYStrategy.java

@@ -0,0 +1,62 @@
+package com.journeyapps.barcodescanner.camera;
+
+import android.graphics.Rect;
+
+import com.journeyapps.barcodescanner.Size;
+
+/**
+ * Scales the size so that it fits exactly. Aspect ratio is NOT preserved.
+ */
+public class FitXYStrategy extends PreviewScalingStrategy {
+    private static final String TAG = FitXYStrategy.class.getSimpleName();
+
+
+    private static float absRatio(float ratio) {
+        if (ratio < 1.0f) {
+            return 1.0f / ratio;
+        } else {
+            return ratio;
+        }
+    }
+
+    /**
+     * Get a score for our size.
+     *
+     * Based on heuristics for penalizing scaling and cropping.
+     *
+     * 1.0 is perfect (exact match).
+     * 0.0 means we can't use it at all.
+     *
+     * @param size the camera preview size (that can be scaled)
+     * @param desired the viewfinder size
+     * @return the score
+     */
+    @Override
+    protected float getScore(Size size, Size desired) {
+        if (size.width <= 0 || size.height <= 0) {
+            return 0f;
+        }
+        float scaleX = absRatio(size.width * 1.0f / desired.width);
+        float scaleY = absRatio(size.height * 1.0f / desired.height);
+
+        float scaleScore = 1.0f / scaleX / scaleY;
+
+        float distortion = absRatio((1.0f * size.width / size.height) / (1.0f * desired.width / desired.height));
+
+        // Distortion is bad!
+        float distortionScore = 1.0f / distortion / distortion / distortion;
+
+        return scaleScore * distortionScore;
+    }
+
+    /**
+     * Scale the preview to match the viewfinder exactly.
+     *
+     * @param previewSize the size of the preview (camera), in current display orientation
+     * @param viewfinderSize the size of the viewfinder (display), in current display orientation
+     * @return a rect placing the preview
+     */
+    public Rect scalePreview(Size previewSize, Size viewfinderSize) {
+        return new Rect(0, 0, viewfinderSize.width, viewfinderSize.height);
+    }
+}

+ 155 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/LegacyPreviewScalingStrategy.java

@@ -0,0 +1,155 @@
+package com.journeyapps.barcodescanner.camera;
+
+import android.graphics.Rect;
+import android.util.Log;
+
+import com.journeyapps.barcodescanner.Size;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ *
+ */
+public class LegacyPreviewScalingStrategy extends PreviewScalingStrategy {
+    private static final String TAG = LegacyPreviewScalingStrategy.class.getSimpleName();
+
+    /**
+     * Choose the best preview size, based on our display size.
+     *
+     * We prefer:
+     * 1. no scaling
+     * 2. least downscaling
+     * 3. least upscaling
+     *
+     * We do not care much about aspect ratio, since we just crop away extra pixels. We only choose
+     * the size to minimize scaling.
+     *
+     * In the future we may consider choosing the biggest possible preview size, to maximize the
+     * resolution we have for decoding. We need more testing to see whether or not that is feasible.
+     *
+     * @param sizes supported preview sizes, containing at least one size. Sizes are in natural camera orientation.
+     * @param desired The desired display size, in the same orientation
+     * @return the best preview size, never null
+     */
+    public Size getBestPreviewSize(List<Size> sizes, final Size desired) {
+        // Sample of supported preview sizes:
+        // http://www.kirill.org/ar/ar.php
+
+        if (desired == null) {
+            return sizes.get(0);
+        }
+
+        Collections.sort(sizes, new Comparator<Size>() {
+            @Override
+            public int compare(Size a, Size b) {
+                Size ascaled = scale(a, desired);
+                int aScale = ascaled.width - a.width;
+                Size bscaled = scale(b, desired);
+                int bScale = bscaled.width - b.width;
+
+                if (aScale == 0 && bScale == 0) {
+                    // Both no scaling, pick the smaller one
+                    return a.compareTo(b);
+                } else if (aScale == 0) {
+                    // No scaling for a; pick a
+                    return -1;
+                } else if (bScale == 0) {
+                    // No scaling for b; pick b
+                    return 1;
+                } else if (aScale < 0 && bScale < 0) {
+                    // Both downscaled. Pick the smaller one (less downscaling).
+                    return a.compareTo(b);
+                } else if (aScale > 0 && bScale > 0) {
+                    // Both upscaled. Pick the larger one (less upscaling).
+                    return -a.compareTo(b);
+                } else if (aScale < 0) {
+                    // a downscaled, b upscaled. Pick a.
+                    return -1;
+                } else {
+                    // a upscaled, b downscaled. Pick b.
+                    return 1;
+                }
+            }
+        });
+
+        Log.i(TAG, "Viewfinder size: " + desired);
+        Log.i(TAG, "Preview in order of preference: " + sizes);
+
+        return sizes.get(0);
+    }
+
+    /**
+     * Scale from so that to.fitsIn(size). Tries to scale by powers of two, or by 3/2. Aspect ratio
+     * is preserved.
+     *
+     * These scaling factors will theoretically result in fast scaling with minimal quality loss.
+     *
+     * TODO: confirm whether or not this is the case in practice.
+     *
+     * @param from the start size
+     * @param to   the minimum desired size
+     * @return the scaled size
+     */
+    public static Size scale(Size from, Size to) {
+        Size current = from;
+
+        if (!to.fitsIn(current)) {
+            // Scale up
+            while (true) {
+                Size scaled150 = current.scale(3, 2);
+                Size scaled200 = current.scale(2, 1);
+                if (to.fitsIn(scaled150)) {
+                    // Scale by 3/2
+                    return scaled150;
+                } else if (to.fitsIn(scaled200)) {
+                    // Scale by 2/1
+                    return scaled200;
+                } else {
+                    // Scale by 2/1 and continue
+                    current = scaled200;
+                }
+            }
+        } else {
+            // Scale down
+            while (true) {
+                Size scaled66 = current.scale(2, 3);
+                Size scaled50 = current.scale(1, 2);
+
+                if (!to.fitsIn(scaled50)) {
+                    if (to.fitsIn(scaled66)) {
+                        // Scale by 2/3
+                        return scaled66;
+                    } else {
+                        // No more downscaling
+                        return current;
+                    }
+                } else {
+                    // Scale by 1/2
+                    current = scaled50;
+                }
+            }
+        }
+    }
+
+    /**
+     * Scale the preview to cover the viewfinder, then center it.
+     *
+     * Aspect ratio is preserved.
+     *
+     * @param previewSize the size of the preview (camera), in current display orientation
+     * @param viewfinderSize the size of the viewfinder (display), in current display orientation
+     * @return a rect placing the preview
+     */
+    public Rect scalePreview(Size previewSize, Size viewfinderSize) {
+        // We avoid scaling if feasible.
+        Size scaledPreview = scale(previewSize, viewfinderSize);
+        Log.i(TAG, "Preview: " + previewSize + "; Scaled: " + scaledPreview + "; Want: " + viewfinderSize);
+
+        int dx = (scaledPreview.width - viewfinderSize.width) / 2;
+        int dy = (scaledPreview.height - viewfinderSize.height) / 2;
+
+        return new Rect(-dx, -dy, scaledPreview.width - dx, scaledPreview.height - dy);
+    }
+}

+ 11 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/PreviewCallback.java

@@ -0,0 +1,11 @@
+package com.journeyapps.barcodescanner.camera;
+
+import com.journeyapps.barcodescanner.SourceData;
+
+/**
+ * Callback for camera previews.
+ */
+public interface PreviewCallback {
+    void onPreview(SourceData sourceData);
+    void onPreviewError(Exception e);
+}

+ 96 - 0
external/zxing/src/main/java/com/journeyapps/barcodescanner/camera/PreviewScalingStrategy.java

@@ -0,0 +1,96 @@
+package com.journeyapps.barcodescanner.camera;
+
+import android.graphics.Rect;
+import android.util.Log;
+
+import com.journeyapps.barcodescanner.Size;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ *
+ */
+public abstract class PreviewScalingStrategy {
+    private static final String TAG = PreviewScalingStrategy.class.getSimpleName();
+
+    /**
+     * Choose the best preview size, based on our viewfinder size.
+     *
+     * The default implementation lets subclasses calculate a score for each size, the picks the one
+     * with the best score.
+     *
+     * The sizes list may be reordered by this call.
+     *
+     * @param sizes supported preview sizes, containing at least one size. Sizes are in natural camera orientation.
+     * @param desired The desired viewfinder size, in the same orientation
+     * @return the best preview size, never null
+     */
+    public Size getBestPreviewSize(List<Size> sizes, final Size desired) {
+        // Sample of supported preview sizes:
+        // http://www.kirill.org/ar/ar.php
+
+        List<Size> ordered = getBestPreviewOrder(sizes, desired);
+
+        Log.i(TAG, "Viewfinder size: " + desired);
+        Log.i(TAG, "Preview in order of preference: " + ordered);
+
+        return ordered.get(0);
+    }
+
+    /**
+     * Sort previews based on their suitability.
+     *
+     * In most cases, {@link #getBestPreviewSize(List, Size)} should be used instead.
+     *
+     * The sizes list may be reordered by this call.
+     *
+     * @param sizes supported preview sizes, containing at least one size. Sizes are in natural camera orientation.
+     * @param desired The desired viewfinder size, in the same orientation
+     * @return an ordered list, best preview first
+     */
+    public List<Size> getBestPreviewOrder(List<Size> sizes, final Size desired) {
+        if (desired == null) {
+            return sizes;
+        }
+
+        Collections.sort(sizes, new Comparator<Size>() {
+            @Override
+            public int compare(Size a, Size b) {
+                float aScore = getScore(a, desired);
+                float bScore = getScore(b, desired);
+                // Bigger score first
+                return Float.compare(bScore, aScore);
+            }
+        });
+
+
+        return sizes;
+    }
+
+    /**
+     * Get a score for our size.
+     *
+     * 1.0 is perfect (exact match).
+     * 0.0 means we can't use it at all.
+     *
+     * Subclasses should override this.
+     *
+     * @param size the camera preview size (that can be scaled)
+     * @param desired the viewfinder size
+     * @return the score
+     */
+    protected float getScore(Size size, Size desired) {
+        return 0.5f;
+    }
+
+    /**
+     * Scale and position the preview relative to the viewfinder.
+     *
+     * @param previewSize the size of the preview (camera), in current display orientation
+     * @param viewfinderSize the size of the viewfinder (display), in current display orientation
+     * @return a rect placing the preview, relative to the viewfinder
+     */
+    public abstract Rect scalePreview(Size previewSize, Size viewfinderSize);
+}

+ 21 - 0
external/zxing/src/main/res/layout/zxing_barcode_scanner.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <com.journeyapps.barcodescanner.BarcodeView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/zxing_barcode_surface"/>
+
+    <com.journeyapps.barcodescanner.ViewfinderView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/zxing_viewfinder_view"/>
+
+    <TextView android:id="@+id/zxing_status_view"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:layout_gravity="bottom|center_horizontal"
+              android:background="@color/zxing_transparent"
+              android:text="@string/zxing_msg_default_status"
+              android:textColor="@color/zxing_status_text"/>
+</merge>

+ 30 - 0
external/zxing/src/main/res/layout/zxing_capture.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2008 ZXing authors
+
+ 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.
+ -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <!--
+    This Activity is typically full-screen. Therefore we can safely use centerCrop scaling with
+    a SurfaceView, without fear of weird artifacts. -->
+    <com.journeyapps.barcodescanner.DecoratedBarcodeView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/zxing_barcode_scanner"
+        app:zxing_preview_scaling_strategy="centerCrop"
+        app:zxing_use_texture_view="false"/>
+
+</merge>

BIN=BIN
external/zxing/src/main/res/raw/zxing_beep.ogg


+ 22 - 0
external/zxing/src/main/res/values-id/zxing_strings.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2014 ZXing authors
+
+ 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.
+ -->
+<resources>
+  <string name="zxing_app_name">Barcode Scanner</string>
+  <string name="zxing_button_ok">OK</string>
+  <string name="zxing_msg_camera_framework_bug">Maaf, kamera mengalami masalah. Anda mungkin perlu me-restart perangkat.</string>
+  <string name="zxing_msg_default_status">Letakkan barcode di dalam kotak jendela untuk memindai.</string>
+</resources>

+ 22 - 0
external/zxing/src/main/res/values-zh/zxing_strings.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2014 ZXing authors
+
+ 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.
+ -->
+<resources>
+  <string name="zxing_app_name">条码扫描器</string>
+  <string name="zxing_button_ok">确定</string>
+  <string name="zxing_msg_camera_framework_bug">抱歉,Android相机出现问题。您可能需要重启设备。</string>
+  <string name="zxing_msg_default_status">请将条码置于取景框内扫描。  </string>
+</resources>

+ 20 - 0
external/zxing/src/main/res/values/zxing_about_library_strings.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="ResourceName,UnusedResources" >
+    <string name="define_zxingandroidembedded" translatable="false" />
+    <!-- Author section -->
+    <string name="library_zxingandroidembedded_author" translatable="false">JourneyApps</string>
+    <string name="library_zxingandroidembedded_authorWebsite" translatable="false">https://journeyapps.com/</string>
+    <!-- Library section -->
+    <string name="library_zxingandroidembedded_libraryName" translatable="false">ZXing Android Embedded</string>
+    <string name="library_zxingandroidembedded_libraryDescription" translatable="false">Barcode scanning library for Android, using ZXing for decoding.</string>
+    <string name="library_zxingandroidembedded_libraryWebsite" translatable="false">https://github.com/journeyapps/zxing-android-embedded</string>
+    <string name="library_zxingandroidembedded_libraryVersion" translatable="false" />
+    <!-- OpenSource section -->
+    <string name="library_zxingandroidembedded_isOpenSource" translatable="false">true</string>
+    <string name="library_zxingandroidembedded_repositoryLink" translatable="false">https://github.com/journeyapps/zxing-android-embedded</string>
+    <!-- License section -->
+    <string name="library_zxingandroidembedded_licenseId" translatable="false">apache_2_0</string>
+    <!-- Custom variables section -->
+</resources>
+

+ 27 - 0
external/zxing/src/main/res/values/zxing_attrs.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources>
+
+  <declare-styleable name="zxing_view">
+    <attr name="zxing_scanner_layout" format="reference"/>
+  </declare-styleable>
+
+  <declare-styleable name="zxing_camera_preview">
+    <attr name="zxing_framing_rect_width" format="dimension" />
+    <attr name="zxing_framing_rect_height" format="dimension" />
+    <attr name="zxing_use_texture_view" format="boolean" />
+    <attr name="zxing_preview_scaling_strategy" format="enum">
+      <enum name="centerCrop" value="1" />
+      <enum name="fitCenter" value="2" />
+      <enum name="fitXY" value="3" />
+    </attr>
+  </declare-styleable>
+
+  <declare-styleable name="zxing_finder">
+    <attr name="zxing_possible_result_points" format="color"/>
+    <attr name="zxing_result_view" format="color"/>
+    <attr name="zxing_viewfinder_laser" format="color"/>
+    <attr name="zxing_viewfinder_mask" format="color"/>
+    <attr name="zxing_viewfinder_laser_visibility" format="boolean"/>
+  </declare-styleable>
+
+</resources>

+ 29 - 0
external/zxing/src/main/res/values/zxing_colors.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2008 ZXing authors
+
+ 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.
+ -->
+<resources>
+  <color name="zxing_possible_result_points">#c0ffbd21</color> <!-- Android standard ICS color -->
+  <color name="zxing_result_view">#b0000000</color>
+  <color name="zxing_status_text">#ffffffff</color>
+  <color name="zxing_transparent">#00000000</color>
+  <color name="zxing_viewfinder_laser">#ffcc0000</color> <!-- Android standard ICS color -->
+  <color name="zxing_viewfinder_mask">#60000000</color>
+
+  <color name="zxing_custom_possible_result_points">#903A89CF</color>
+  <color name="zxing_custom_result_view">#b0000000</color>
+  <color name="zxing_custom_viewfinder_laser">#1976D2</color>
+  <color name="zxing_custom_viewfinder_mask">#60000000</color>
+</resources>

+ 27 - 0
external/zxing/src/main/res/values/zxing_ids.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2008 ZXing authors
+
+ 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.
+ -->
+<resources>
+  <item type="id" name="zxing_decode"/>
+  <item type="id" name="zxing_preview_failed"/>
+  <item type="id" name="zxing_decode_failed"/>
+  <item type="id" name="zxing_decode_succeeded"/>
+  <item type="id" name="zxing_possible_result_points"/>
+  <item type="id" name="zxing_back_button"/>
+  <item type="id" name="zxing_prewiew_size_ready"/>
+  <item type="id" name="zxing_camera_error"/>
+  <item type="id" name="zxing_camera_closed"/>
+</resources>

+ 22 - 0
external/zxing/src/main/res/values/zxing_strings.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2014 ZXing authors
+
+ 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.
+ -->
+<resources>
+  <string name="zxing_app_name">Barcode Scanner</string>
+  <string name="zxing_button_ok">OK</string>
+  <string name="zxing_msg_camera_framework_bug">Sorry, the Android camera encountered a problem. You may need to restart the device.</string>
+  <string name="zxing_msg_default_status">Place a barcode inside the viewfinder rectangle to scan it.</string>
+</resources>

+ 5 - 0
external/zxing/src/main/res/values/zxing_themes.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources>
+  <style name="zxing_CaptureTheme" parent="android:Theme.Holo.NoActionBar.Fullscreen">
+  </style>
+</resources>

+ 1 - 1
frame/bom/build.gradle

@@ -6,7 +6,7 @@ plugins {
 ext {
     GROUP_ID = 'com.wenext.android'
     ARTIFACT_ID = 'frame-bom'
-    VERSION = '6.1.0'
+    VERSION = '6.1.1'
 }
 
 dependencies {

+ 6 - 0
gradle/libs.versions.toml

@@ -144,6 +144,9 @@ frameZero = "2.0.0-weparty-agp-beta"
 # utilcodex
 utilcodex = "1.31.1"
 
+# zxing
+zxing = "3.4.1"
+
 [libraries]
 # plugin
 android-build-gradle = { module = "com.android.tools.build:gradle", version.ref = "agp" }
@@ -310,6 +313,9 @@ frame-zero = { group = "com.wenext.android", name = "frame-zero", version.ref =
 # utilcodex
 utilcodex = { group = "com.blankj", name = "utilcodex", version.ref = "utilcodex" }
 
+# zxing
+zxing = { group = "com.google.zxing", name = "core", version.ref = "zxing" }
+
 # test
 junit = { group = "junit", name = "junit", version.ref = "junit" }
 androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }

+ 9 - 8
settings.gradle

@@ -59,6 +59,15 @@ dependencyResolutionManagement {
 
 include ':app'
 
+include 'external:libcocos2dx'
+include 'external:retrofit'
+include 'external:SVGAPlayer'
+include 'external:animplayer'
+include 'external:drawee'
+include 'external:AndroidAutoSize'
+include 'external:tcturing'
+include 'external:zxing'
+
 include 'frame:zero'
 include 'frame:base'
 include 'frame:aab'
@@ -107,14 +116,6 @@ include 'frame:bom'
 include 'frame:startup'
 include 'frame:effectpreview'
 include 'frame:bundletool'
-
-include 'external:libcocos2dx'
-include 'external:retrofit'
-include 'external:SVGAPlayer'
-include 'external:animplayer'
-include 'external:drawee'
-include 'external:AndroidAutoSize'
-include 'external:tcturing'
 include ':frame:zegortc'
 include 'frame:trace:trace-api'
 include 'frame:trace:trace-plugin'