From ee23fe93b7da3022f56df99addf2387d426fa9fa Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Apr 2019 11:45:00 +0500 Subject: [PATCH] Add --- .gitignore | 13 + .idea/codeStyles/Project.xml | 29 + .idea/gradle.xml | 15 + .idea/misc.xml | 14 + .idea/runConfigurations.xml | 12 + .idea/vcs.xml | 6 + app/.gitignore | 1 + app/build.gradle | 31 + app/proguard-rules.pro | 21 + .../ExampleInstrumentedTest.java | 26 + app/src/main/AndroidManifest.xml | 29 + .../barcodescanner/MainActivity.java | 40 + .../BarcodeGraphic.java | 124 ++ .../BarcodeGraphicTracker.java | 85 ++ .../BarcodeTrackerFactory.java | 49 + .../materialbarcodescanner/CameraSource.java | 1185 +++++++++++++++++ .../CameraSourcePreview.java | 201 +++ .../GraphicOverlay.java | 205 +++ .../MaterialBarcodeScanner.java | 122 ++ .../MaterialBarcodeScannerActivity.java | 240 ++++ .../MaterialBarcodeScannerBuilder.java | 331 +++++ .../SoundPoolPlayer.java | 29 + .../drawable-v24/ic_launcher_foreground.xml | 34 + .../res/drawable/ic_flash_off_white_24dp.png | Bin 0 -> 223 bytes .../res/drawable/ic_flash_on_white_24dp.png | Bin 0 -> 158 bytes .../res/drawable/ic_launcher_background.xml | 170 +++ app/src/main/res/drawable/ic_qrcode.png | Bin 0 -> 271 bytes .../drawable/material_barcode_square_512.png | Bin 0 -> 9300 bytes .../material_barcode_square_512_green.png | Bin 0 -> 9176 bytes .../main/res/layout-land/barcode_capture.xml | 64 + app/src/main/res/layout/actions_main.xml | 13 + app/src/main/res/layout/activity_main.xml | 19 + app/src/main/res/layout/barcode_capture.xml | 65 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2963 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4905 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6895 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10413 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15132 bytes app/src/main/res/raw/bleep.wav | Bin 0 -> 47104 bytes app/src/main/res/values/colors.xml | 4 + app/src/main/res/values/strings.xml | 6 + app/src/main/res/values/styles.xml | 14 + .../barcodescanner/ExampleUnitTest.java | 17 + build.gradle | 32 + gradle.properties | 15 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++ gradlew.bat | 84 ++ settings.gradle | 1 + 57 files changed, 3534 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 .idea/vcs.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/uz/theairsoft/barcodescanner/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/uz/theairsoft/barcodescanner/MainActivity.java create mode 100644 app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/BarcodeGraphic.java create mode 100644 app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/BarcodeGraphicTracker.java create mode 100644 app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/BarcodeTrackerFactory.java create mode 100644 app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/CameraSource.java create mode 100644 app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/CameraSourcePreview.java create mode 100644 app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/GraphicOverlay.java create mode 100644 app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/MaterialBarcodeScanner.java create mode 100644 app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/MaterialBarcodeScannerActivity.java create mode 100644 app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/MaterialBarcodeScannerBuilder.java create mode 100644 app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/SoundPoolPlayer.java create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_flash_off_white_24dp.png create mode 100644 app/src/main/res/drawable/ic_flash_on_white_24dp.png create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_qrcode.png create mode 100644 app/src/main/res/drawable/material_barcode_square_512.png create mode 100644 app/src/main/res/drawable/material_barcode_square_512_green.png create mode 100644 app/src/main/res/layout-land/barcode_capture.xml create mode 100644 app/src/main/res/layout/actions_main.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/barcode_capture.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/raw/bleep.wav create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/test/java/uz/theairsoft/barcodescanner/ExampleUnitTest.java create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b75303 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..30aa626 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..2996d53 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..af0bbdd --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..543a984 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.application' +android { + compileSdkVersion 28 + defaultConfig { + applicationId "uz.theairsoft.barcodescanner" + minSdkVersion 18 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support:design:28.0.0' + implementation 'com.google.android.gms:play-services-vision:17.0.2' + implementation 'org.greenrobot:eventbus:3.0.0' + implementation 'junit:junit:4.13-beta-1' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/uz/theairsoft/barcodescanner/ExampleInstrumentedTest.java b/app/src/androidTest/java/uz/theairsoft/barcodescanner/ExampleInstrumentedTest.java new file mode 100644 index 0000000..5b8fa4b --- /dev/null +++ b/app/src/androidTest/java/uz/theairsoft/barcodescanner/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package uz.theairsoft.barcodescanner; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("uz.theairsoft.barcodescanner", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bb0a0a1 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/uz/theairsoft/barcodescanner/MainActivity.java b/app/src/main/java/uz/theairsoft/barcodescanner/MainActivity.java new file mode 100644 index 0000000..6a2b173 --- /dev/null +++ b/app/src/main/java/uz/theairsoft/barcodescanner/MainActivity.java @@ -0,0 +1,40 @@ +package uz.theairsoft.barcodescanner; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +import com.google.android.gms.vision.barcode.Barcode; + +import uz.theairsoft.barcodescanner.materialbarcodescanner.MaterialBarcodeScanner; +import uz.theairsoft.barcodescanner.materialbarcodescanner.MaterialBarcodeScannerBuilder; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } + private void startScan() { + /** + * Build a new MaterialBarcodeScanner + */ + final MaterialBarcodeScanner materialBarcodeScanner = new MaterialBarcodeScannerBuilder() + .withActivity(MainActivity.this) + .withEnableAutoFocus(true) + .withBleepEnabled(true) + .withBackfacingCamera() + .withCenterTracker() + .withText("Scanning...") + .withResultListener(new MaterialBarcodeScanner.OnResultListener() { + @Override + public void onResult(Barcode barcode) { + + } + }) + .build(); + materialBarcodeScanner.setActivityCompat(this); + materialBarcodeScanner.setContainerId(R.id.content); + materialBarcodeScanner.startScan(); + } +} diff --git a/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/BarcodeGraphic.java b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/BarcodeGraphic.java new file mode 100644 index 0000000..8ca341e --- /dev/null +++ b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/BarcodeGraphic.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) The Android Open Source Project + * + * 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 uz.theairsoft.barcodescanner.materialbarcodescanner; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; + +import com.google.android.gms.vision.barcode.Barcode; + +/** + * Graphic instance for rendering barcode position, size, and ID within an associated graphic + * overlay view. + */ +public class BarcodeGraphic extends GraphicOverlay.Graphic { + + private int mId; + + private static int mCurrentColorIndex = 0; + + private Paint mRectPaint; + private Paint mTextPaint; + private volatile Barcode mBarcode; + + private int mStrokeWidth = 24; + private int mCornerWidth = 64; + private int mCorderPadding = mStrokeWidth / 2; + + public BarcodeGraphic(GraphicOverlay overlay, final int trackerColor) { + super(overlay); + + mRectPaint = new Paint(); + mRectPaint.setColor(trackerColor); + mRectPaint.setStyle(Paint.Style.STROKE); + mRectPaint.setStrokeWidth(mStrokeWidth); + //mRectPaint.setAlpha(100); + + mTextPaint = new Paint(); + mTextPaint.setColor(trackerColor); + mTextPaint.setFakeBoldText(true); + mTextPaint.setTextSize(46.0f); + } + + public int getId() { + return mId; + } + + public void setId(int id) { + this.mId = id; + } + + public Barcode getBarcode() { + return mBarcode; + } + + /** + * Updates the barcode instance from the detection of the most recent frame. Invalidates the + * relevant portions of the overlay to trigger a redraw. + */ + void updateItem(Barcode barcode) { + mBarcode = barcode; + postInvalidate(); + } + + /** + * Draws the barcode annotations for position, size, and raw value on the supplied canvas. + */ + @Override + public void draw(Canvas canvas) { + Barcode barcode = mBarcode; + if (barcode == null) { + return; + } + + // Draws the bounding box around the barcode. + RectF rect = new RectF(barcode.getBoundingBox()); + rect.left = translateX(rect.left); + rect.top = translateY(rect.top); + rect.right = translateX(rect.right); + rect.bottom = translateY(rect.bottom); + + //canvas.drawRect(rect, mRectPaint); + + /** + * Draw the top left corner + */ + canvas.drawLine(rect.left - mCorderPadding, rect.top, rect.left + mCornerWidth, rect.top, mRectPaint); + canvas.drawLine(rect.left, rect.top, rect.left, rect.top + mCornerWidth, mRectPaint); + + /** + * Draw the bottom left corner + */ + canvas.drawLine(rect.left, rect.bottom, rect.left, rect.bottom - mCornerWidth, mRectPaint); + canvas.drawLine(rect.left - mCorderPadding, rect.bottom, rect.left + mCornerWidth, rect.bottom, mRectPaint); + + /** + * Draw the top right corner + */ + canvas.drawLine(rect.right + mCorderPadding, rect.top, rect.right - mCornerWidth, rect.top, mRectPaint); + canvas.drawLine(rect.right, rect.top, rect.right, rect.top + mCornerWidth, mRectPaint); + + /** + * Draw the bottom right corner + */ + canvas.drawLine(rect.right + mCorderPadding, rect.bottom, rect.right - mCornerWidth, rect.bottom, mRectPaint); + canvas.drawLine(rect.right, rect.bottom, rect.right, rect.bottom - mCornerWidth, mRectPaint); + + // Draws a label at the bottom of the barcode indicate the barcode value that was detected. + canvas.drawText(barcode.displayValue, rect.left, rect.bottom + 100, mTextPaint); + } +} diff --git a/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/BarcodeGraphicTracker.java b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/BarcodeGraphicTracker.java new file mode 100644 index 0000000..1a0fa0a --- /dev/null +++ b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/BarcodeGraphicTracker.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) The Android Open Source Project + * + * 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 uz.theairsoft.barcodescanner.materialbarcodescanner; + +import com.google.android.gms.vision.Detector; +import com.google.android.gms.vision.Tracker; +import com.google.android.gms.vision.barcode.Barcode; + +/** + * Generic tracker which is used for tracking or reading a barcode (and can really be used for + * any type of item). This is used to receive newly detected items, add a graphical representation + * to an overlay, update the graphics as the item changes, and remove the graphics when the item + * goes away. + */ +public class BarcodeGraphicTracker extends Tracker { + + private NewDetectionListener mListener; + private GraphicOverlay mOverlay; + private BarcodeGraphic mGraphic; + + BarcodeGraphicTracker(GraphicOverlay overlay, BarcodeGraphic graphic) { + mOverlay = overlay; + mGraphic = graphic; + } + + public void setListener(NewDetectionListener mListener) { + this.mListener = mListener; + } + + /** + * Start tracking the detected item instance within the item overlay. + */ + @Override + public void onNewItem(int id, Barcode item) { + mGraphic.setId(id); + if (mListener != null){ + mListener.onNewDetection(item); + } + } + + /** + * Update the position/characteristics of the item within the overlay. + */ + @Override + public void onUpdate(Detector.Detections detectionResults, Barcode item) { + mOverlay.add(mGraphic); + mGraphic.updateItem(item); + } + + /** + * Hide the graphic when the corresponding object was not detected. This can happen for + * intermediate frames temporarily, for example if the object was momentarily blocked from + * view. + */ + @Override + public void onMissing(Detector.Detections detectionResults) { + mOverlay.remove(mGraphic); + } + + /** + * Called when the item is assumed to be gone for good. Remove the graphic annotation from + * the overlay. + */ + @Override + public void onDone() { + mOverlay.remove(mGraphic); + } + + public interface NewDetectionListener { + void onNewDetection(Barcode barcode); + } +} diff --git a/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/BarcodeTrackerFactory.java b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/BarcodeTrackerFactory.java new file mode 100644 index 0000000..981e249 --- /dev/null +++ b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/BarcodeTrackerFactory.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) The Android Open Source Project + * + * 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 uz.theairsoft.barcodescanner.materialbarcodescanner; + +import com.google.android.gms.vision.MultiProcessor; +import com.google.android.gms.vision.Tracker; +import com.google.android.gms.vision.barcode.Barcode; + +/** + * Factory for creating a tracker and associated graphic to be associated with a new barcode. The + * multi-processor uses this factory to create barcode trackers as needed -- one for each barcode. + */ +public class BarcodeTrackerFactory implements MultiProcessor.Factory { + + private GraphicOverlay mGraphicOverlay; + private BarcodeGraphicTracker.NewDetectionListener mDetectionListener; + private int mTrackerColor; + + BarcodeTrackerFactory(GraphicOverlay barcodeGraphicOverlay, BarcodeGraphicTracker.NewDetectionListener listener, int trackerColor) { + mGraphicOverlay = barcodeGraphicOverlay; + mDetectionListener = listener; + mTrackerColor = trackerColor; + } + + @Override + public Tracker create(Barcode barcode) { + BarcodeGraphic graphic = new BarcodeGraphic(mGraphicOverlay, mTrackerColor); + BarcodeGraphicTracker tracker = new BarcodeGraphicTracker(mGraphicOverlay, graphic); + if (mDetectionListener != null){ + tracker.setListener(mDetectionListener); + } + return tracker; + } + +} + diff --git a/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/CameraSource.java b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/CameraSource.java new file mode 100644 index 0000000..9622771 --- /dev/null +++ b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/CameraSource.java @@ -0,0 +1,1185 @@ +package uz.theairsoft.barcodescanner.materialbarcodescanner; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.ImageFormat; +import android.graphics.SurfaceTexture; +import android.hardware.Camera; +import android.hardware.Camera.CameraInfo; +import android.os.Build; +import android.os.SystemClock; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresPermission; +import android.support.annotation.StringDef; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.WindowManager; + +import com.google.android.gms.common.images.Size; +import com.google.android.gms.vision.Detector; +import com.google.android.gms.vision.Frame; + +import java.io.IOException; +import java.lang.Thread.State; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +// Note: This requires Google Play Services 8.1 or higher, due to using indirect byte buffers for +// storing images. + +/** + * Manages the camera in conjunction with an underlying + * {@link Detector}. This receives preview frames from the camera at + * a specified rate, sending those frames to the detector as fast as it is able to process those + * frames. + * This camera source makes a best effort to manage processing on preview frames as fast as + * possible, while at the same time minimizing lag. As such, frames may be dropped if the detector + * is unable to keep up with the rate of frames generated by the camera. You should use + * {@link Builder#setRequestedFps(float)} to specify a frame rate that works well with + * the capabilities of the camera hardware and the detector options that you have selected. If CPU + * utilization is higher than you'd like, then you may want to consider reducing FPS. If the camera + * preview or detector results are too "jerky", then you may want to consider increasing FPS. + * The following Android permission is required to use the camera: + *
    + *
  • android.permissions.CAMERA
  • + *
+ */ +@SuppressWarnings("deprecation") +public class CameraSource { + @SuppressLint("InlinedApi") + public static final int CAMERA_FACING_BACK = CameraInfo.CAMERA_FACING_BACK; + @SuppressLint("InlinedApi") + public static final int CAMERA_FACING_FRONT = CameraInfo.CAMERA_FACING_FRONT; + + private static final String TAG = "OpenCameraSource"; + + /** + * The dummy surface texture must be assigned a chosen name. Since we never use an OpenGL + * context, we can choose any ID we want here. + */ + private static final int DUMMY_TEXTURE_NAME = 100; + + /** + * If the absolute difference between a preview size aspect ratio and a picture size aspect + * ratio is less than this tolerance, they are considered to be the same aspect ratio. + */ + private static final float ASPECT_RATIO_TOLERANCE = 0.01f; + + @StringDef({ + Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE, + Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, + Camera.Parameters.FOCUS_MODE_AUTO, + Camera.Parameters.FOCUS_MODE_EDOF, + Camera.Parameters.FOCUS_MODE_FIXED, + Camera.Parameters.FOCUS_MODE_INFINITY, + Camera.Parameters.FOCUS_MODE_MACRO + }) + @Retention(RetentionPolicy.SOURCE) + private @interface FocusMode {} + + @StringDef({ + Camera.Parameters.FLASH_MODE_ON, + Camera.Parameters.FLASH_MODE_OFF, + Camera.Parameters.FLASH_MODE_AUTO, + Camera.Parameters.FLASH_MODE_RED_EYE, + Camera.Parameters.FLASH_MODE_TORCH + }) + @Retention(RetentionPolicy.SOURCE) + private @interface FlashMode {} + + private Context mContext; + + private final Object mCameraLock = new Object(); + + // Guarded by mCameraLock + private Camera mCamera; + + private int mFacing = CAMERA_FACING_BACK; + + /** + * Rotation of the device, and thus the associated preview images captured from the device. + * See {@link Frame.Metadata#getRotation()}. + */ + private int mRotation; + + private Size mPreviewSize; + + // These values may be requested by the caller. Due to hardware limitations, we may need to + // select close, but not exactly the same values for these. + private float mRequestedFps = 30.0f; + private int mRequestedPreviewWidth = 1024; + private int mRequestedPreviewHeight = 768; + + + private String mFocusMode = null; + private String mFlashMode = null; + + // These instances need to be held onto to avoid GC of their underlying resources. Even though + // these aren't used outside of the method that creates them, they still must have hard + // references maintained to them. + private SurfaceView mDummySurfaceView; + private SurfaceTexture mDummySurfaceTexture; + + /** + * Dedicated thread and associated runnable for calling into the detector with frames, as the + * frames become available from the camera. + */ + private Thread mProcessingThread; + private FrameProcessingRunnable mFrameProcessor; + + /** + * Map to convert between a byte array, received from the camera, and its associated byte + * buffer. We use byte buffers internally because this is a more efficient way to call into + * native code later (avoids a potential copy). + */ + private Map mBytesToByteBuffer = new HashMap<>(); + + //============================================================================================== + // Builder + //============================================================================================== + + /** + * Builder for configuring and creating an associated camera source. + */ + public static class Builder { + private final Detector mDetector; + private CameraSource mCameraSource = new CameraSource(); + + /** + * Creates a camera source builder with the supplied context and detector. Camera preview + * images will be streamed to the associated detector upon starting the camera source. + */ + public Builder(Context context, Detector detector) { + if (context == null) { + throw new IllegalArgumentException("No context supplied."); + } + if (detector == null) { + throw new IllegalArgumentException("No detector supplied."); + } + + mDetector = detector; + mCameraSource.mContext = context; + } + + /** + * Sets the requested frame rate in frames per second. If the exact requested value is not + * not available, the best matching available value is selected. Default: 30. + */ + public Builder setRequestedFps(float fps) { + if (fps <= 0) { + throw new IllegalArgumentException("Invalid fps: " + fps); + } + mCameraSource.mRequestedFps = fps; + return this; + } + + public Builder setFocusMode(@FocusMode String mode) { + mCameraSource.mFocusMode = mode; + return this; + } + + public Builder setFlashMode(@FlashMode String mode) { + mCameraSource.mFlashMode = mode; + return this; + } + + /** + * Sets the desired width and height of the camera frames in pixels. If the exact desired + * values are not available options, the best matching available options are selected. + * Also, we try to select a preview size which corresponds to the aspect ratio of an + * associated full picture size, if applicable. Default: 1024x768. + */ + public Builder setRequestedPreviewSize(int width, int height) { + // Restrict the requested range to something within the realm of possibility. The + // choice of 1000000 is a bit arbitrary -- intended to be well beyond resolutions that + // devices can support. We bound this to avoid int overflow in the code later. + final int MAX = 1000000; + if ((width <= 0) || (width > MAX) || (height <= 0) || (height > MAX)) { + throw new IllegalArgumentException("Invalid preview size: " + width + "x" + height); + } + mCameraSource.mRequestedPreviewWidth = width; + mCameraSource.mRequestedPreviewHeight = height; + return this; + } + + /** + * Sets the camera to use (either {@link #CAMERA_FACING_BACK} or + * {@link #CAMERA_FACING_FRONT}). Default: back facing. + */ + public Builder setFacing(int facing) { + if ((facing != CAMERA_FACING_BACK) && (facing != CAMERA_FACING_FRONT)) { + throw new IllegalArgumentException("Invalid camera: " + facing); + } + mCameraSource.mFacing = facing; + return this; + } + + /** + * Creates an instance of the camera source. + */ + public CameraSource build() { + mCameraSource.mFrameProcessor = mCameraSource.new FrameProcessingRunnable(mDetector); + return mCameraSource; + } + } + + //============================================================================================== + // Bridge Functionality for the Camera1 API + //============================================================================================== + + /** + * Callback interface used to signal the moment of actual image capture. + */ + public interface ShutterCallback { + /** + * Called as near as possible to the moment when a photo is captured from the sensor. This + * is a good opportunity to play a shutter sound or give other feedback of camera operation. + * This may be some time after the photo was triggered, but some time before the actual data + * is available. + */ + void onShutter(); + } + + /** + * Callback interface used to supply image data from a photo capture. + */ + public interface PictureCallback { + /** + * Called when image data is available after a picture is taken. The format of the data + * is a jpeg binary. + */ + void onPictureTaken(byte[] data); + } + + /** + * Callback interface used to notify on completion of camera auto focus. + */ + public interface AutoFocusCallback { + /** + * Called when the camera auto focus completes. If the camera + * does not support auto-focus and autoFocus is called, + * onAutoFocus will be called immediately with a fake value of + * success set to true. + * The auto-focus routine does not lock auto-exposure and auto-white + * balance after it completes. + * + * @param success true if focus was successful, false if otherwise + */ + void onAutoFocus(boolean success); + } + + /** + * Callback interface used to notify on auto focus start and stop. + * This is only supported in continuous autofocus modes -- {@link + * Camera.Parameters#FOCUS_MODE_CONTINUOUS_VIDEO} and {@link + * Camera.Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can show + * autofocus animation based on this + */ + public interface AutoFocusMoveCallback { + /** + * Called when the camera auto focus starts or stops. + * + * @param start true if focus starts to move, false if focus stops to move + */ + void onAutoFocusMoving(boolean start); + } + + //============================================================================================== + // Public + //============================================================================================== + + /** + * Stops the camera and releases the resources of the camera and underlying detector. + */ + public void release() { + synchronized (mCameraLock) { + stop(); + mFrameProcessor.release(); + } + } + + /** + * Opens the camera and starts sending preview frames to the underlying detector. The preview + * frames are not displayed. + * + * @throws IOException if the camera's preview texture or display could not be initialized + */ + @RequiresPermission(Manifest.permission.CAMERA) + public CameraSource start() throws IOException { + synchronized (mCameraLock) { + if (mCamera != null) { + return this; + } + + mCamera = createCamera(); + + // SurfaceTexture was introduced in Honeycomb (11), so if we are running and + // old version of Android. fall back to use SurfaceView. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mDummySurfaceTexture = new SurfaceTexture(DUMMY_TEXTURE_NAME); + mCamera.setPreviewTexture(mDummySurfaceTexture); + } else { + mDummySurfaceView = new SurfaceView(mContext); + mCamera.setPreviewDisplay(mDummySurfaceView.getHolder()); + } + mCamera.startPreview(); + + mProcessingThread = new Thread(mFrameProcessor); + mFrameProcessor.setActive(true); + mProcessingThread.start(); + } + return this; + } + + /** + * Opens the camera and starts sending preview frames to the underlying detector. The supplied + * surface holder is used for the preview so frames can be displayed to the user. + * + * @param surfaceHolder the surface holder to use for the preview frames + * @throws IOException if the supplied surface holder could not be used as the preview display + */ + @RequiresPermission(Manifest.permission.CAMERA) + public CameraSource start(SurfaceHolder surfaceHolder) throws IOException { + synchronized (mCameraLock) { + if (mCamera != null) { + return this; + } + + mCamera = createCamera(); + mCamera.setPreviewDisplay(surfaceHolder); + mCamera.startPreview(); + + mProcessingThread = new Thread(mFrameProcessor); + mFrameProcessor.setActive(true); + mProcessingThread.start(); + } + return this; + } + + /** + * Closes the camera and stops sending frames to the underlying frame detector. + * This camera source may be restarted again by calling {@link #start()} or + * {@link #start(SurfaceHolder)}. + * Call {@link #release()} instead to completely shut down this camera source and release the + * resources of the underlying detector. + */ + public void stop() { + synchronized (mCameraLock) { + mFrameProcessor.setActive(false); + if (mProcessingThread != null) { + try { + // Wait for the thread to complete to ensure that we can't have multiple threads + // executing at the same time (i.e., which would happen if we called start too + // quickly after stop). + mProcessingThread.join(); + } catch (InterruptedException e) { + Log.d(TAG, "Frame processing thread interrupted on release."); + } + mProcessingThread = null; + } + + // clear the buffer to prevent oom exceptions + mBytesToByteBuffer.clear(); + + if (mCamera != null) { + mCamera.stopPreview(); + mCamera.setPreviewCallbackWithBuffer(null); + try { + // We want to be compatible back to Gingerbread, but SurfaceTexture + // wasn't introduced until Honeycomb. Since the interface cannot use a SurfaceTexture, if the + // developer wants to display a preview we must use a SurfaceHolder. If the developer doesn't + // want to display a preview we use a SurfaceTexture if we are running at least Honeycomb. + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mCamera.setPreviewTexture(null); + + } else { + mCamera.setPreviewDisplay(null); + } + } catch (Exception e) { + Log.e(TAG, "Failed to clear camera preview: " + e); + } + mCamera.release(); + mCamera = null; + } + } + } + + /** + * Returns the preview size that is currently in use by the underlying camera. + */ + public Size getPreviewSize() { + return mPreviewSize; + } + + /** + * Returns the selected camera; one of {@link #CAMERA_FACING_BACK} or + * {@link #CAMERA_FACING_FRONT}. + */ + public int getCameraFacing() { + return mFacing; + } + + public int doZoom(float scale) { + synchronized (mCameraLock) { + if (mCamera == null) { + return 0; + } + int currentZoom = 0; + int maxZoom; + Camera.Parameters parameters = mCamera.getParameters(); + if (!parameters.isZoomSupported()) { + Log.w(TAG, "Zoom is not supported on this device"); + return currentZoom; + } + maxZoom = parameters.getMaxZoom(); + + currentZoom = parameters.getZoom() + 1; + float newZoom; + if (scale > 1) { + newZoom = currentZoom + scale * (maxZoom / 10); + } else { + newZoom = currentZoom * scale; + } + currentZoom = Math.round(newZoom) - 1; + if (currentZoom < 0) { + currentZoom = 0; + } else if (currentZoom > maxZoom) { + currentZoom = maxZoom; + } + parameters.setZoom(currentZoom); + mCamera.setParameters(parameters); + return currentZoom; + } + } + + /** + * Initiates taking a picture, which happens asynchronously. The camera source should have been + * activated previously with {@link #start()} or {@link #start(SurfaceHolder)}. The camera + * preview is suspended while the picture is being taken, but will resume once picture taking is + * done. + * + * @param shutter the callback for image capture moment, or null + * @param jpeg the callback for JPEG image data, or null + */ + public void takePicture(ShutterCallback shutter, PictureCallback jpeg) { + synchronized (mCameraLock) { + if (mCamera != null) { + PictureStartCallback startCallback = new PictureStartCallback(); + startCallback.mDelegate = shutter; + PictureDoneCallback doneCallback = new PictureDoneCallback(); + doneCallback.mDelegate = jpeg; + mCamera.takePicture(startCallback, null, null, doneCallback); + } + } + } + + /** + * Gets the current focus mode setting. + * + * @return current focus mode. This value is null if the camera is not yet created. Applications should call {@link + * #autoFocus(AutoFocusCallback)} to start the focus if focus + * mode is FOCUS_MODE_AUTO or FOCUS_MODE_MACRO. + * @see Camera.Parameters#FOCUS_MODE_AUTO + * @see Camera.Parameters#FOCUS_MODE_INFINITY + * @see Camera.Parameters#FOCUS_MODE_MACRO + * @see Camera.Parameters#FOCUS_MODE_FIXED + * @see Camera.Parameters#FOCUS_MODE_EDOF + * @see Camera.Parameters#FOCUS_MODE_CONTINUOUS_VIDEO + * @see Camera.Parameters#FOCUS_MODE_CONTINUOUS_PICTURE + */ + @Nullable + @FocusMode + public String getFocusMode() { + return mFocusMode; + } + + /** + * Sets the focus mode. + * + * @param mode the focus mode + * @return {@code true} if the focus mode is set, {@code false} otherwise + * @see #getFocusMode() + */ + public boolean setFocusMode(@FocusMode String mode) { + synchronized (mCameraLock) { + if (mCamera != null && mode != null) { + Camera.Parameters parameters = mCamera.getParameters(); + if (parameters.getSupportedFocusModes().contains(mode)) { + parameters.setFocusMode(mode); + mCamera.setParameters(parameters); + mFocusMode = mode; + return true; + } + } + + return false; + } + } + + /** + * Gets the current flash mode setting. + * + * @return current flash mode. null if flash mode setting is not + * supported or the camera is not yet created. + * @see Camera.Parameters#FLASH_MODE_OFF + * @see Camera.Parameters#FLASH_MODE_AUTO + * @see Camera.Parameters#FLASH_MODE_ON + * @see Camera.Parameters#FLASH_MODE_RED_EYE + * @see Camera.Parameters#FLASH_MODE_TORCH + */ + @Nullable + @FlashMode + public String getFlashMode() { + return mFlashMode; + } + + /** + * Sets the flash mode. + * + * @param mode flash mode. + * @return {@code true} if the flash mode is set, {@code false} otherwise + * @see #getFlashMode() + */ + public boolean setFlashMode(@FlashMode String mode) { + synchronized (mCameraLock) { + if (mCamera != null && mode != null) { + Camera.Parameters parameters = mCamera.getParameters(); + final List supportedFlashModes = parameters.getSupportedFlashModes(); + if (supportedFlashModes != null && supportedFlashModes.contains(mode)) { + parameters.setFlashMode(mode); + mCamera.setParameters(parameters); + mFlashMode = mode; + return true; + } + } + + return false; + } + } + + /** + * Starts camera auto-focus and registers a callback function to run when + * the camera is focused. This method is only valid when preview is active + * (between {@link #start()} or {@link #start(SurfaceHolder)} and before {@link #stop()} or {@link #release()}). + * Callers should check + * {@link #getFocusMode()} to determine if + * this method should be called. If the camera does not support auto-focus, + * it is a no-op and {@link AutoFocusCallback#onAutoFocus(boolean)} + * callback will be called immediately. + * If the current flash mode is not + * {@link Camera.Parameters#FLASH_MODE_OFF}, flash may be + * fired during auto-focus, depending on the driver and camera hardware. + * + * @param cb the callback to run + * @see #cancelAutoFocus() + */ + public void autoFocus(@Nullable AutoFocusCallback cb) { + synchronized (mCameraLock) { + if (mCamera != null) { + CameraAutoFocusCallback autoFocusCallback = null; + if (cb != null) { + autoFocusCallback = new CameraAutoFocusCallback(); + autoFocusCallback.mDelegate = cb; + } + mCamera.autoFocus(autoFocusCallback); + } + } + } + + /** + * Cancels any auto-focus function in progress. + * Whether or not auto-focus is currently in progress, + * this function will return the focus position to the default. + * If the camera does not support auto-focus, this is a no-op. + * + * @see #autoFocus(AutoFocusCallback) + */ + public void cancelAutoFocus() { + synchronized (mCameraLock) { + if (mCamera != null) { + mCamera.cancelAutoFocus(); + } + } + } + + /** + * Sets camera auto-focus move callback. + * + * @param cb the callback to run + * @return {@code true} if the operation is supported (i.e. from Jelly Bean), {@code false} otherwise + */ + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public boolean setAutoFocusMoveCallback(@Nullable AutoFocusMoveCallback cb) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + return false; + } + + synchronized (mCameraLock) { + if (mCamera != null) { + CameraAutoFocusMoveCallback autoFocusMoveCallback = null; + if (cb != null) { + autoFocusMoveCallback = new CameraAutoFocusMoveCallback(); + autoFocusMoveCallback.mDelegate = cb; + } + mCamera.setAutoFocusMoveCallback(autoFocusMoveCallback); + } + } + + return true; + } + + //============================================================================================== + // Private + //============================================================================================== + + /** + * Only allow creation via the builder class. + */ + private CameraSource() { + } + + /** + * Wraps the camera1 shutter callback so that the deprecated API isn't exposed. + */ + private class PictureStartCallback implements Camera.ShutterCallback { + private ShutterCallback mDelegate; + + @Override + public void onShutter() { + if (mDelegate != null) { + mDelegate.onShutter(); + } + } + } + + /** + * Wraps the final callback in the camera sequence, so that we can automatically turn the camera + * preview back on after the picture has been taken. + */ + private class PictureDoneCallback implements Camera.PictureCallback { + private PictureCallback mDelegate; + + @Override + public void onPictureTaken(byte[] data, Camera camera) { + if (mDelegate != null) { + mDelegate.onPictureTaken(data); + } + synchronized (mCameraLock) { + if (mCamera != null) { + mCamera.startPreview(); + } + } + } + } + + /** + * Wraps the camera1 auto focus callback so that the deprecated API isn't exposed. + */ + private class CameraAutoFocusCallback implements Camera.AutoFocusCallback { + private AutoFocusCallback mDelegate; + + @Override + public void onAutoFocus(boolean success, Camera camera) { + if (mDelegate != null) { + mDelegate.onAutoFocus(success); + } + } + } + + /** + * Wraps the camera1 auto focus move callback so that the deprecated API isn't exposed. + */ + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + private class CameraAutoFocusMoveCallback implements Camera.AutoFocusMoveCallback { + private AutoFocusMoveCallback mDelegate; + + @Override + public void onAutoFocusMoving(boolean start, Camera camera) { + if (mDelegate != null) { + mDelegate.onAutoFocusMoving(start); + } + } + } + + /** + * Opens the camera and applies the user settings. + * + * @throws RuntimeException if the method fails + */ + @SuppressLint("InlinedApi") + private Camera createCamera() { + int requestedCameraId = getIdForRequestedCamera(mFacing); + if (requestedCameraId == -1) { + throw new RuntimeException("Could not find requested camera."); + } + Camera camera = Camera.open(requestedCameraId); + + SizePair sizePair = selectSizePair(camera, mRequestedPreviewWidth, mRequestedPreviewHeight); + if (sizePair == null) { + throw new RuntimeException("Could not find suitable preview size."); + } + Size pictureSize = sizePair.pictureSize(); + mPreviewSize = sizePair.previewSize(); + + int[] previewFpsRange = selectPreviewFpsRange(camera, mRequestedFps); + if (previewFpsRange == null) { + throw new RuntimeException("Could not find suitable preview frames per second range."); + } + + Camera.Parameters parameters = camera.getParameters(); + + if (pictureSize != null) { + parameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); + } + + parameters.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); + parameters.setPreviewFpsRange( + previewFpsRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], + previewFpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); + parameters.setPreviewFormat(ImageFormat.NV21); + + setRotation(camera, parameters, requestedCameraId); + + if (mFocusMode != null) { + if (parameters.getSupportedFocusModes().contains( + mFocusMode)) { + parameters.setFocusMode(mFocusMode); + } else { + Log.i(TAG, "Camera focus mode: " + mFocusMode + " is not supported on this device."); + } + } + + // setting mFocusMode to the one set in the params + mFocusMode = parameters.getFocusMode(); + + if (mFlashMode != null) { + if (parameters.getSupportedFlashModes().contains( + mFlashMode)) { + parameters.setFlashMode(mFlashMode); + } else { + Log.i(TAG, "Camera flash mode: " + mFlashMode + " is not supported on this device."); + } + } + + // setting mFlashMode to the one set in the params + mFlashMode = parameters.getFlashMode(); + + camera.setParameters(parameters); + + // Four frame buffers are needed for working with the camera: + // + // one for the frame that is currently being executed upon in doing detection + // one for the next pending frame to process immediately upon completing detection + // two for the frames that the camera uses to populate future preview images + camera.setPreviewCallbackWithBuffer(new CameraPreviewCallback()); + camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); + camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); + camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); + camera.addCallbackBuffer(createPreviewBuffer(mPreviewSize)); + + return camera; + } + + /** + * Gets the id for the camera specified by the direction it is facing. Returns -1 if no such + * camera was found. + * + * @param facing the desired camera (front-facing or rear-facing) + */ + private static int getIdForRequestedCamera(int facing) { + CameraInfo cameraInfo = new CameraInfo(); + for (int i = 0; i < Camera.getNumberOfCameras(); ++i) { + Camera.getCameraInfo(i, cameraInfo); + if (cameraInfo.facing == facing) { + return i; + } + } + return -1; + } + + /** + * Selects the most suitable preview and picture size, given the desired width and height. + * Even though we may only need the preview size, it's necessary to find both the preview + * size and the picture size of the camera together, because these need to have the same aspect + * ratio. On some hardware, if you would only set the preview size, you will get a distorted + * image. + * + * @param camera the camera to select a preview size from + * @param desiredWidth the desired width of the camera preview frames + * @param desiredHeight the desired height of the camera preview frames + * @return the selected preview and picture size pair + */ + private static SizePair selectSizePair(Camera camera, int desiredWidth, int desiredHeight) { + List validPreviewSizes = generateValidPreviewSizeList(camera); + + // The method for selecting the best size is to minimize the sum of the differences between + // the desired values and the actual values for width and height. This is certainly not the + // only way to select the best size, but it provides a decent tradeoff between using the + // closest aspect ratio vs. using the closest pixel area. + SizePair selectedPair = null; + int minDiff = Integer.MAX_VALUE; + for (SizePair sizePair : validPreviewSizes) { + Size size = sizePair.previewSize(); + int diff = Math.abs(size.getWidth() - desiredWidth) + + Math.abs(size.getHeight() - desiredHeight); + if (diff < minDiff) { + selectedPair = sizePair; + minDiff = diff; + } + } + + return selectedPair; + } + + /** + * Stores a preview size and a corresponding same-aspect-ratio picture size. To avoid distorted + * preview images on some devices, the picture size must be set to a size that is the same + * aspect ratio as the preview size or the preview may end up being distorted. If the picture + * size is null, then there is no picture size with the same aspect ratio as the preview size. + */ + private static class SizePair { + private Size mPreview; + private Size mPicture; + + public SizePair(Camera.Size previewSize, + Camera.Size pictureSize) { + mPreview = new Size(previewSize.width, previewSize.height); + if (pictureSize != null) { + mPicture = new Size(pictureSize.width, pictureSize.height); + } + } + + public Size previewSize() { + return mPreview; + } + + @SuppressWarnings("unused") + public Size pictureSize() { + return mPicture; + } + } + + /** + * Generates a list of acceptable preview sizes. Preview sizes are not acceptable if there is + * not a corresponding picture size of the same aspect ratio. If there is a corresponding + * picture size of the same aspect ratio, the picture size is paired up with the preview size. + * This is necessary because even if we don't use still pictures, the still picture size must be + * set to a size that is the same aspect ratio as the preview size we choose. Otherwise, the + * preview images may be distorted on some devices. + */ + private static List generateValidPreviewSizeList(Camera camera) { + Camera.Parameters parameters = camera.getParameters(); + List supportedPreviewSizes = + parameters.getSupportedPreviewSizes(); + List supportedPictureSizes = + parameters.getSupportedPictureSizes(); + List validPreviewSizes = new ArrayList<>(); + for (Camera.Size previewSize : supportedPreviewSizes) { + float previewAspectRatio = (float) previewSize.width / (float) previewSize.height; + + // By looping through the picture sizes in order, we favor the higher resolutions. + // We choose the highest resolution in order to support taking the full resolution + // picture later. + for (Camera.Size pictureSize : supportedPictureSizes) { + float pictureAspectRatio = (float) pictureSize.width / (float) pictureSize.height; + if (Math.abs(previewAspectRatio - pictureAspectRatio) < ASPECT_RATIO_TOLERANCE) { + validPreviewSizes.add(new SizePair(previewSize, pictureSize)); + break; + } + } + } + + // If there are no picture sizes with the same aspect ratio as any preview sizes, allow all + // of the preview sizes and hope that the camera can handle it. Probably unlikely, but we + // still account for it. + if (validPreviewSizes.size() == 0) { + Log.w(TAG, "No preview sizes have a corresponding same-aspect-ratio picture size"); + for (Camera.Size previewSize : supportedPreviewSizes) { + // The null picture size will let us know that we shouldn't set a picture size. + validPreviewSizes.add(new SizePair(previewSize, null)); + } + } + + return validPreviewSizes; + } + + /** + * Selects the most suitable preview frames per second range, given the desired frames per + * second. + * + * @param camera the camera to select a frames per second range from + * @param desiredPreviewFps the desired frames per second for the camera preview frames + * @return the selected preview frames per second range + */ + private int[] selectPreviewFpsRange(Camera camera, float desiredPreviewFps) { + // The camera API uses integers scaled by a factor of 1000 instead of floating-point frame + // rates. + int desiredPreviewFpsScaled = (int) (desiredPreviewFps * 1000.0f); + + // The method for selecting the best range is to minimize the sum of the differences between + // the desired value and the upper and lower bounds of the range. This may select a range + // that the desired value is outside of, but this is often preferred. For example, if the + // desired frame rate is 29.97, the range (30, 30) is probably more desirable than the + // range (15, 30). + int[] selectedFpsRange = null; + int minDiff = Integer.MAX_VALUE; + List previewFpsRangeList = camera.getParameters().getSupportedPreviewFpsRange(); + for (int[] range : previewFpsRangeList) { + int deltaMin = desiredPreviewFpsScaled - range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX]; + int deltaMax = desiredPreviewFpsScaled - range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]; + int diff = Math.abs(deltaMin) + Math.abs(deltaMax); + if (diff < minDiff) { + selectedFpsRange = range; + minDiff = diff; + } + } + return selectedFpsRange; + } + + /** + * Calculates the correct rotation for the given camera id and sets the rotation in the + * parameters. It also sets the camera's display orientation and rotation. + * + * @param parameters the camera parameters for which to set the rotation + * @param cameraId the camera id to set rotation based on + */ + private void setRotation(Camera camera, Camera.Parameters parameters, int cameraId) { + WindowManager windowManager = + (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + int degrees = 0; + int rotation = windowManager.getDefaultDisplay().getRotation(); + 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; + default: + Log.e(TAG, "Bad rotation value: " + rotation); + } + + CameraInfo cameraInfo = new CameraInfo(); + Camera.getCameraInfo(cameraId, cameraInfo); + + int angle; + int displayAngle; + if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) { + angle = (cameraInfo.orientation + degrees) % 360; + displayAngle = (360 - angle); // compensate for it being mirrored + } else { // back-facing + angle = (cameraInfo.orientation - degrees + 360) % 360; + displayAngle = angle; + } + + // This corresponds to the rotation constants in {@link Frame}. + mRotation = angle / 90; + + camera.setDisplayOrientation(displayAngle); + parameters.setRotation(angle); + } + + /** + * Creates one buffer for the camera preview callback. The size of the buffer is based off of + * the camera preview size and the format of the camera image. + * + * @return a new preview buffer of the appropriate size for the current camera settings + */ + private byte[] createPreviewBuffer(Size previewSize) { + int bitsPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.NV21); + long sizeInBits = previewSize.getHeight() * previewSize.getWidth() * bitsPerPixel; + int bufferSize = (int) Math.ceil(sizeInBits / 8.0d) + 1; + + // + // NOTICE: This code only works when using play services v. 8.1 or higher. + // + + // Creating the byte array this way and wrapping it, as opposed to using .allocate(), + // should guarantee that there will be an array to work with. + byte[] byteArray = new byte[bufferSize]; + ByteBuffer buffer = ByteBuffer.wrap(byteArray); + if (!buffer.hasArray() || (buffer.array() != byteArray)) { + // I don't think that this will ever happen. But if it does, then we wouldn't be + // passing the preview content to the underlying detector later. + throw new IllegalStateException("Failed to create valid buffer for camera source."); + } + + mBytesToByteBuffer.put(byteArray, buffer); + return byteArray; + } + + //============================================================================================== + // Frame processing + //============================================================================================== + + /** + * Called when the camera has a new preview frame. + */ + private class CameraPreviewCallback implements Camera.PreviewCallback { + @Override + public void onPreviewFrame(byte[] data, Camera camera) { + mFrameProcessor.setNextFrame(data, camera); + } + } + + /** + * This runnable controls access to the underlying receiver, calling it to process frames when + * available from the camera. This is designed to run detection on frames as fast as possible + * (i.e., without unnecessary context switching or waiting on the next frame). + * While detection is running on a frame, new frames may be received from the camera. As these + * frames come in, the most recent frame is held onto as pending. As soon as detection and its + * associated processing are done for the previous frame, detection on the mostly recently + * received frame will immediately start on the same thread. + */ + private class FrameProcessingRunnable implements Runnable { + private Detector mDetector; + private long mStartTimeMillis = SystemClock.elapsedRealtime(); + + // This lock guards all of the member variables below. + private final Object mLock = new Object(); + private boolean mActive = true; + + // These pending variables hold the state associated with the new frame awaiting processing. + private long mPendingTimeMillis; + private int mPendingFrameId = 0; + private ByteBuffer mPendingFrameData; + + FrameProcessingRunnable(Detector detector) { + mDetector = detector; + } + + /** + * Releases the underlying receiver. This is only safe to do after the associated thread + * has completed, which is managed in camera source's release method above. + */ + @SuppressLint("Assert") + void release() { + assert (mProcessingThread.getState() == State.TERMINATED); + mDetector.release(); + mDetector = null; + } + + /** + * Marks the runnable as active/not active. Signals any blocked threads to continue. + */ + void setActive(boolean active) { + synchronized (mLock) { + mActive = active; + mLock.notifyAll(); + } + } + + /** + * Sets the frame data received from the camera. This adds the previous unused frame buffer + * (if present) back to the camera, and keeps a pending reference to the frame data for + * future use. + */ + void setNextFrame(byte[] data, Camera camera) { + synchronized (mLock) { + if (mPendingFrameData != null) { + camera.addCallbackBuffer(mPendingFrameData.array()); + mPendingFrameData = null; + } + + if (!mBytesToByteBuffer.containsKey(data)) { + Log.d(TAG, + "Skipping frame. Could not find ByteBuffer associated with the image " + + "data from the camera."); + return; + } + + // Timestamp and frame ID are maintained here, which will give downstream code some + // idea of the timing of frames received and when frames were dropped along the way. + mPendingTimeMillis = SystemClock.elapsedRealtime() - mStartTimeMillis; + mPendingFrameId++; + mPendingFrameData = mBytesToByteBuffer.get(data); + + // Notify the processor thread if it is waiting on the next frame (see below). + mLock.notifyAll(); + } + } + + /** + * As long as the processing thread is active, this executes detection on frames + * continuously. The next pending frame is either immediately available or hasn't been + * received yet. Once it is available, we transfer the frame info to local variables and + * run detection on that frame. It immediately loops back for the next frame without + * pausing. + * If detection takes longer than the time in between new frames from the camera, this will + * mean that this loop will run without ever waiting on a frame, avoiding any context + * switching or frame acquisition time latency. + * If you find that this is using more CPU than you'd like, you should probably decrease the + * FPS setting above to allow for some idle time in between frames. + */ + @Override + public void run() { + Frame outputFrame; + ByteBuffer data; + + while (true) { + synchronized (mLock) { + while (mActive && (mPendingFrameData == null)) { + try { + // Wait for the next frame to be received from the camera, since we + // don't have it yet. + mLock.wait(); + } catch (InterruptedException e) { + Log.d(TAG, "Frame processing loop terminated.", e); + return; + } + } + + if (!mActive) { + // Exit the loop once this camera source is stopped or released. We check + // this here, immediately after the wait() above, to handle the case where + // setActive(false) had been called, triggering the termination of this + // loop. + return; + } + + outputFrame = new Frame.Builder() + .setImageData(mPendingFrameData, mPreviewSize.getWidth(), + mPreviewSize.getHeight(), ImageFormat.NV21) + .setId(mPendingFrameId) + .setTimestampMillis(mPendingTimeMillis) + .setRotation(mRotation) + .build(); + + // Hold onto the frame data locally, so that we can use this for detection + // below. We need to clear mPendingFrameData to ensure that this buffer isn't + // recycled back to the camera before we are done using that data. + data = mPendingFrameData; + mPendingFrameData = null; + } + + // The code below needs to run outside of synchronization, because this will allow + // the camera to add pending frame(s) while we are running detection on the current + // frame. + + try { + mDetector.receiveFrame(outputFrame); + } catch (Throwable t) { + Log.e(TAG, "Exception thrown from receiver.", t); + } finally { + mCamera.addCallbackBuffer(data.array()); + } + } + } + } +} diff --git a/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/CameraSourcePreview.java b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/CameraSourcePreview.java new file mode 100644 index 0000000..8587834 --- /dev/null +++ b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/CameraSourcePreview.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) The Android Open Source Project + * + * 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 uz.theairsoft.barcodescanner.materialbarcodescanner; + +import android.Manifest; +import android.content.Context; +import android.content.res.Configuration; +import android.support.annotation.RequiresPermission; +import android.util.AttributeSet; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.ViewGroup; + +import com.google.android.gms.common.images.Size; + +import java.io.IOException; + +public class CameraSourcePreview extends ViewGroup { + private static final String TAG = "CameraSourcePreview"; + + private Context mContext; + private SurfaceView mSurfaceView; + private boolean mStartRequested; + private boolean mSurfaceAvailable; + private CameraSource mCameraSource; + + private GraphicOverlay mOverlay; + + public CameraSourcePreview(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + mStartRequested = false; + mSurfaceAvailable = false; + + mSurfaceView = new SurfaceView(context); + mSurfaceView.getHolder().addCallback(new SurfaceCallback()); + addView(mSurfaceView); + } + + @RequiresPermission(Manifest.permission.CAMERA) + public void start(CameraSource cameraSource) throws IOException, SecurityException { + if (cameraSource == null) { + stop(); + } + + mCameraSource = cameraSource; + + if (mCameraSource != null) { + mStartRequested = true; + startIfReady(); + } + } + + @RequiresPermission(Manifest.permission.CAMERA) + public void start(CameraSource cameraSource, GraphicOverlay overlay) throws IOException, SecurityException { + mOverlay = overlay; + start(cameraSource); + } + + public void stop() { + if (mCameraSource != null) { + mCameraSource.stop(); + } + } + + public void release() { + if (mCameraSource != null) { + mCameraSource.release(); + } + } + + @RequiresPermission(Manifest.permission.CAMERA) + private void startIfReady() throws IOException, SecurityException { + if (mStartRequested && mSurfaceAvailable) { + mCameraSource.start(mSurfaceView.getHolder()); + if (mOverlay != null) { + Size size = mCameraSource.getPreviewSize(); + int min = Math.min(size.getWidth(), size.getHeight()); + int max = Math.max(size.getWidth(), size.getHeight()); + if (isPortraitMode()) { + // Swap width and height sizes when in portrait, since it will be rotated by + // 90 degrees + mOverlay.setCameraInfo(min, max, mCameraSource.getCameraFacing()); + } else { + mOverlay.setCameraInfo(max, min, mCameraSource.getCameraFacing()); + } + mOverlay.clear(); + } + mStartRequested = false; + } + } + + private class SurfaceCallback implements SurfaceHolder.Callback { + @Override + public void surfaceCreated(SurfaceHolder surface) { + mSurfaceAvailable = true; + try { + startIfReady(); + } catch (SecurityException se) { + Log.e(TAG,"Do not have permission to start the camera", se); + } catch (IOException e) { + Log.e(TAG, "Could not start camera source.", e); + } + } + + @Override + public void surfaceDestroyed(SurfaceHolder surface) { + mSurfaceAvailable = false; + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int previewWidth = 320; + int previewHeight = 240; + if (mCameraSource != null) { + Size size = mCameraSource.getPreviewSize(); + if (size != null) { + previewWidth = size.getWidth(); + previewHeight = size.getHeight(); + } + } + + // Swap width and height sizes when in portrait, since it will be rotated 90 degrees + if (isPortraitMode()) { + int tmp = previewWidth; + previewWidth = previewHeight; + previewHeight = tmp; + } + + final int viewWidth = right - left; + final int viewHeight = bottom - top; + + int childWidth; + int childHeight; + int childXOffset = 0; + int childYOffset = 0; + float widthRatio = (float) viewWidth / (float) previewWidth; + float heightRatio = (float) viewHeight / (float) previewHeight; + + // To fill the view with the camera preview, while also preserving the correct aspect ratio, + // it is usually necessary to slightly oversize the child and to crop off portions along one + // of the dimensions. We scale up based on the dimension requiring the most correction, and + // compute a crop offset for the other dimension. + if (widthRatio > heightRatio) { + childWidth = viewWidth; + childHeight = (int) ((float) previewHeight * widthRatio); + childYOffset = (childHeight - viewHeight) / 2; + } else { + childWidth = (int) ((float) previewWidth * heightRatio); + childHeight = viewHeight; + childXOffset = (childWidth - viewWidth) / 2; + } + + for (int i = 0; i < getChildCount(); ++i) { + // One dimension will be cropped. We shift child over or up by this offset and adjust + // the size to maintain the proper aspect ratio. + getChildAt(i).layout( + -1 * childXOffset, -1 * childYOffset, + childWidth - childXOffset, childHeight - childYOffset); + } + + try { + startIfReady(); + } catch (IOException e) { + Log.e(TAG, "Could not start camera source.", e); + } + } + + + private boolean isPortraitMode() { + int orientation = mContext.getResources().getConfiguration().orientation; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + return false; + } + if (orientation == Configuration.ORIENTATION_PORTRAIT) { + return true; + } + + Log.d(TAG, "isPortraitMode returning false by default"); + return false; + } +} diff --git a/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/GraphicOverlay.java b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/GraphicOverlay.java new file mode 100644 index 0000000..3f389e2 --- /dev/null +++ b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/GraphicOverlay.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) The Android Open Source Project + * + * 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 uz.theairsoft.barcodescanner.materialbarcodescanner; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.View; + +import com.google.android.gms.vision.CameraSource; + +import java.util.HashSet; +import java.util.Set; + +/** + * A view which renders a series of custom graphics to be overlayed on top of an associated preview + * (i.e., the camera preview). The creator can add graphics objects, update the objects, and remove + * them, triggering the appropriate drawing and invalidation within the view.

+ * + * Supports scaling and mirroring of the graphics relative the camera's preview properties. The + * idea is that detection items are expressed in terms of a preview size, but need to be scaled up + * to the full view size, and also mirrored in the case of the front-facing camera.

+ * + * Associated {@link Graphic} items should use the following methods to convert to view coordinates + * for the graphics that are drawn: + *

    + *
  1. {@link Graphic#scaleX(float)} and {@link Graphic#scaleY(float)} adjust the size of the + * supplied value from the preview scale to the view scale.
  2. + *
  3. {@link Graphic#translateX(float)} and {@link Graphic#translateY(float)} adjust the coordinate + * from the preview's coordinate system to the view coordinate system.
  4. + *
+ */ +public class GraphicOverlay extends View { + private final Object mLock = new Object(); + private int mPreviewWidth; + private float mWidthScaleFactor = 1.0f; + private int mPreviewHeight; + private float mHeightScaleFactor = 1.0f; + private int mFacing = CameraSource.CAMERA_FACING_BACK; + private Set mGraphics = new HashSet<>(); + private T mFirstGraphic; + + /** + * Base class for a custom graphics object to be rendered within the graphic overlay. Subclass + * this and implement the {@link Graphic#draw(Canvas)} method to define the + * graphics element. Add instances to the overlay using {@link GraphicOverlay#add(Graphic)}. + */ + public static abstract class Graphic { + private GraphicOverlay mOverlay; + + public Graphic(GraphicOverlay overlay) { + mOverlay = overlay; + } + + /** + * Draw the graphic on the supplied canvas. Drawing should use the following methods to + * convert to view coordinates for the graphics that are drawn: + *
    + *
  1. {@link Graphic#scaleX(float)} and {@link Graphic#scaleY(float)} adjust the size of + * the supplied value from the preview scale to the view scale.
  2. + *
  3. {@link Graphic#translateX(float)} and {@link Graphic#translateY(float)} adjust the + * coordinate from the preview's coordinate system to the view coordinate system.
  4. + *
+ * + * @param canvas drawing canvas + */ + public abstract void draw(Canvas canvas); + + /** + * Adjusts a horizontal value of the supplied value from the preview scale to the view + * scale. + */ + public float scaleX(float horizontal) { + return horizontal * mOverlay.mWidthScaleFactor; + } + + /** + * Adjusts a vertical value of the supplied value from the preview scale to the view scale. + */ + public float scaleY(float vertical) { + return vertical * mOverlay.mHeightScaleFactor; + } + + /** + * Adjusts the x coordinate from the preview's coordinate system to the view coordinate + * system. + */ + public float translateX(float x) { + if (mOverlay.mFacing == CameraSource.CAMERA_FACING_FRONT) { + return mOverlay.getWidth() - scaleX(x); + } else { + return scaleX(x); + } + } + + /** + * Adjusts the y coordinate from the preview's coordinate system to the view coordinate + * system. + */ + public float translateY(float y) { + return scaleY(y); + } + + public void postInvalidate() { + mOverlay.postInvalidate(); + } + } + + public GraphicOverlay(Context context, AttributeSet attrs) { + super(context, attrs); + } + + /** + * Removes all graphics from the overlay. + */ + public void clear() { + synchronized (mLock) { + mGraphics.clear(); + mFirstGraphic = null; + } + postInvalidate(); + } + + /** + * Adds a graphic to the overlay. + */ + public void add(T graphic) { + synchronized (mLock) { + mGraphics.add(graphic); + if (mFirstGraphic == null) { + mFirstGraphic = graphic; + } + } + postInvalidate(); + } + + /** + * Removes a graphic from the overlay. + */ + public void remove(T graphic) { + synchronized (mLock) { + mGraphics.remove(graphic); + if (mFirstGraphic != null && mFirstGraphic.equals(graphic)) { + mFirstGraphic = null; + } + } + postInvalidate(); + } + + /** + * Returns the first (oldest) graphic added. This is used + * to get the barcode that was detected first. + * @return graphic containing the barcode, or null if no barcodes are detected. + */ + public T getFirstGraphic() { + synchronized (mLock) { + return mFirstGraphic; + } + } + + /** + * Sets the camera attributes for size and facing direction, which informs how to transform + * image coordinates later. + */ + public void setCameraInfo(int previewWidth, int previewHeight, int facing) { + synchronized (mLock) { + mPreviewWidth = previewWidth; + mPreviewHeight = previewHeight; + mFacing = facing; + } + postInvalidate(); + } + + /** + * Draws the overlay with its associated graphic objects. + */ + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + synchronized (mLock) { + if ((mPreviewWidth != 0) && (mPreviewHeight != 0)) { + mWidthScaleFactor = (float) canvas.getWidth() / (float) mPreviewWidth; + mHeightScaleFactor = (float) canvas.getHeight() / (float) mPreviewHeight; + } + + for (Graphic graphic : mGraphics) { + graphic.draw(canvas); + } + } + } +} diff --git a/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/MaterialBarcodeScanner.java b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/MaterialBarcodeScanner.java new file mode 100644 index 0000000..742f222 --- /dev/null +++ b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/MaterialBarcodeScanner.java @@ -0,0 +1,122 @@ +package uz.theairsoft.barcodescanner.materialbarcodescanner; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.support.annotation.NonNull; +import android.support.design.widget.Snackbar; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.FrameLayout; + +import com.google.android.gms.vision.barcode.Barcode; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import uz.theairsoft.barcodescanner.R; + +public class MaterialBarcodeScanner { + private AppCompatActivity activityCompat; + private int containerId; + + public void setActivityCompat(AppCompatActivity activityCompat) { + this.activityCompat = activityCompat; + } + + public void setContainerId(int containerId) { + this.containerId = containerId; + } + + /** + * Request codes + */ + public static final int RC_HANDLE_CAMERA_PERM = 2; + + /** + * Scanner modes + */ + public static final int SCANNER_MODE_FREE = 1; + public static final int SCANNER_MODE_CENTER = 2; + + protected final MaterialBarcodeScannerBuilder mMaterialBarcodeScannerBuilder; + + private FrameLayout mContentView; //Content frame for fragments + + private OnResultListener onResultListener; + + public MaterialBarcodeScanner(@NonNull MaterialBarcodeScannerBuilder materialBarcodeScannerBuilder) { + this.mMaterialBarcodeScannerBuilder = materialBarcodeScannerBuilder; + } + + public void setOnResultListener(OnResultListener onResultListener) { + this.onResultListener = onResultListener; + } + + @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) + public void onBarcodeScannerResult(Barcode barcode) { + onResultListener.onResult(barcode); + EventBus.getDefault().removeStickyEvent(barcode); + EventBus.getDefault().unregister(this); + mMaterialBarcodeScannerBuilder.clean(); + } + + /** + * Interface definition for a callback to be invoked when a view is clicked. + */ + public interface OnResultListener { + void onResult(Barcode barcode); + } + + /** + * Start a scan for a barcode + *

+ * This opens a new activity with the parameters provided by the MaterialBarcodeScannerBuilder + */ + public void startScan() { + EventBus.getDefault().register(this); + if (mMaterialBarcodeScannerBuilder.getActivity() == null) { + throw new RuntimeException("Could not start scan: Activity reference lost (please rebuild the MaterialBarcodeScanner before calling startScan)"); + } + int mCameraPermission = ActivityCompat.checkSelfPermission(mMaterialBarcodeScannerBuilder.getActivity(), Manifest.permission.CAMERA); + if (mCameraPermission != PackageManager.PERMISSION_GRANTED) { + requestCameraPermission(); + } else { + //Open activity + EventBus.getDefault().postSticky(this); + FragmentTransaction transaction = activityCompat.getSupportFragmentManager().beginTransaction(); + transaction.add(containerId, new MaterialBarcodeScannerActivity()); + transaction.addToBackStack(null); + transaction.commit(); +// Intent intent = new Intent(mMaterialBarcodeScannerBuilder.getActivity(), MaterialBarcodeScannerActivity.class); +// mMaterialBarcodeScannerBuilder.getActivity().startActivity(intent); + } + } + + private void requestCameraPermission() { + final String[] mPermissions = new String[]{Manifest.permission.CAMERA}; + if (!ActivityCompat.shouldShowRequestPermissionRationale(mMaterialBarcodeScannerBuilder.getActivity(), Manifest.permission.CAMERA)) { + ActivityCompat.requestPermissions(mMaterialBarcodeScannerBuilder.getActivity(), mPermissions, RC_HANDLE_CAMERA_PERM); + return; + } + View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(View view) { + ActivityCompat.requestPermissions(mMaterialBarcodeScannerBuilder.getActivity(), mPermissions, RC_HANDLE_CAMERA_PERM); + } + }; + Snackbar.make(mMaterialBarcodeScannerBuilder.mRootView, R.string.permission_camera_rationale, + Snackbar.LENGTH_INDEFINITE) + .setAction(android.R.string.ok, listener) + .show(); + } + + public MaterialBarcodeScannerBuilder getMaterialBarcodeScannerBuilder() { + return mMaterialBarcodeScannerBuilder; + } + +} diff --git a/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/MaterialBarcodeScannerActivity.java b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/MaterialBarcodeScannerActivity.java new file mode 100644 index 0000000..d2e03c7 --- /dev/null +++ b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/MaterialBarcodeScannerActivity.java @@ -0,0 +1,240 @@ +package uz.theairsoft.barcodescanner.materialbarcodescanner; + +import android.app.Dialog; +import android.hardware.Camera; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; +import com.google.android.gms.vision.MultiProcessor; +import com.google.android.gms.vision.barcode.Barcode; +import com.google.android.gms.vision.barcode.BarcodeDetector; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.io.IOException; + +import uz.theairsoft.barcodescanner.R; + +import static org.junit.Assert.*; + +public class MaterialBarcodeScannerActivity extends Fragment { + + private static final int RC_HANDLE_GMS = 9001; + + private static final String TAG = "MaterialBarcodeScanner"; + + private MaterialBarcodeScanner mMaterialBarcodeScanner; + private MaterialBarcodeScannerBuilder mMaterialBarcodeScannerBuilder; + + private BarcodeDetector barcodeDetector; + + private CameraSourcePreview mCameraSourcePreview; + + private GraphicOverlay mGraphicOverlay; + + private SoundPoolPlayer mSoundPoolPlayer; + + /** + * true if no further barcode should be detected or given as a result + */ + private boolean mDetectionConsumed = false; + + private boolean mFlashOn = false; + + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.barcode_capture,container,false); + } + + + @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) + public void onMaterialBarcodeScanner(MaterialBarcodeScanner materialBarcodeScanner){ + this.mMaterialBarcodeScanner = materialBarcodeScanner; + mMaterialBarcodeScannerBuilder = mMaterialBarcodeScanner.getMaterialBarcodeScannerBuilder(); + barcodeDetector = mMaterialBarcodeScanner.getMaterialBarcodeScannerBuilder().getBarcodeDetector(); + startCameraSource(); + setupLayout(); + } + + private void setupLayout() { + final TextView topTextView = (TextView) getView().findViewById(R.id.topText); + assertNotNull(topTextView); + String topText = mMaterialBarcodeScannerBuilder.getText(); + if(!mMaterialBarcodeScannerBuilder.getText().equals("")){ + topTextView.setText(topText); + } + setupButtons(); + setupCenterTracker(); + } + + private void setupCenterTracker() { + if(mMaterialBarcodeScannerBuilder.getScannerMode() == MaterialBarcodeScanner.SCANNER_MODE_CENTER){ + final ImageView centerTracker = (ImageView) getView().findViewById(R.id.barcode_square); + centerTracker.setImageResource(mMaterialBarcodeScannerBuilder.getTrackerResourceID()); + mGraphicOverlay.setVisibility(View.INVISIBLE); + } + } + + private void updateCenterTrackerForDetectedState() { + if(mMaterialBarcodeScannerBuilder.getScannerMode() == MaterialBarcodeScanner.SCANNER_MODE_CENTER){ + final ImageView centerTracker = (ImageView) getView().findViewById(R.id.barcode_square); + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + centerTracker.setImageResource(mMaterialBarcodeScannerBuilder.getTrackerDetectedResourceID()); + } + }); + } + } + + private void setupButtons() { + final LinearLayout flashOnButton = (LinearLayout)getView().findViewById(R.id.flashIconButton); + final ImageView flashToggleIcon = (ImageView)getView().findViewById(R.id.flashIcon); + assertNotNull(flashOnButton); + flashOnButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mFlashOn) { + flashToggleIcon.setBackgroundResource(R.drawable.ic_flash_on_white_24dp); + disableTorch(); + } else { + flashToggleIcon.setBackgroundResource(R.drawable.ic_flash_off_white_24dp); + enableTorch(); + } + mFlashOn ^= true; + } + }); + if(mMaterialBarcodeScannerBuilder.isFlashEnabledByDefault()){ + flashToggleIcon.setBackgroundResource(R.drawable.ic_flash_off_white_24dp); + } + } + + /** + * Starts or restarts the camera source, if it exists. If the camera source doesn't exist yet + * (e.g., because onResume was called before the camera source was created), this will be called + * again when the camera source is created. + */ + private void startCameraSource() throws SecurityException { + // check that the device has play services available. + mSoundPoolPlayer = new SoundPoolPlayer(getContext()); + int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable( + getActivity().getApplicationContext()); + if (code != ConnectionResult.SUCCESS) { + Dialog dialog = GoogleApiAvailability.getInstance().getErrorDialog(getActivity(), code, RC_HANDLE_GMS); + dialog.show(); + } + mGraphicOverlay = (GraphicOverlay)getView().findViewById(R.id.graphicOverlay); + BarcodeGraphicTracker.NewDetectionListener listener = new BarcodeGraphicTracker.NewDetectionListener() { + @Override + public void onNewDetection(Barcode barcode) { + if(!mDetectionConsumed){ + mDetectionConsumed = true; + Log.d(TAG, "Barcode detected! - " + barcode.displayValue); + EventBus.getDefault().postSticky(barcode); + updateCenterTrackerForDetectedState(); + if(mMaterialBarcodeScannerBuilder.isBleepEnabled()){ + mSoundPoolPlayer.playShortResource(R.raw.bleep); + } + mGraphicOverlay.postDelayed(new Runnable() { + @Override + public void run() { + getActivity().getSupportFragmentManager().popBackStack(); + } + },50); + } + } + }; + BarcodeTrackerFactory barcodeFactory = new BarcodeTrackerFactory(mGraphicOverlay, listener, mMaterialBarcodeScannerBuilder.getTrackerColor()); + barcodeDetector.setProcessor(new MultiProcessor.Builder<>(barcodeFactory).build()); + CameraSource mCameraSource = mMaterialBarcodeScannerBuilder.getCameraSource(); + if (mCameraSource != null) { + try { + mCameraSourcePreview = (CameraSourcePreview) getView().findViewById(R.id.preview); + mCameraSourcePreview.start(mCameraSource, mGraphicOverlay); + } catch (IOException e) { + Log.e(TAG, "Unable to start camera source.", e); + mCameraSource.release(); + mCameraSource = null; + } + } + } + + private void enableTorch() throws SecurityException{ + mMaterialBarcodeScannerBuilder.getCameraSource().setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); + try { + mMaterialBarcodeScannerBuilder.getCameraSource().start(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void disableTorch() throws SecurityException{ + mMaterialBarcodeScannerBuilder.getCameraSource().setFlashMode(Camera.Parameters.FLASH_MODE_OFF); + try { + mMaterialBarcodeScannerBuilder.getCameraSource().start(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onStart() { + super.onStart(); + EventBus.getDefault().register(this); + } + + @Override + public void onStop() { + EventBus.getDefault().unregister(this); + super.onStop(); + } + + /** + * Stops the camera. + */ + @Override + public void onPause() { + super.onPause(); + if (mCameraSourcePreview != null) { + mCameraSourcePreview.stop(); + } + } + + /** + * Releases the resources associated with the camera source, the associated detectors, and the + * rest of the processing pipeline. + */ + @Override + public void onDestroy() { + super.onDestroy(); + if(getActivity().isFinishing()){ + clean(); + } + } + + private void clean() { + EventBus.getDefault().removeStickyEvent(MaterialBarcodeScanner.class); + if (mCameraSourcePreview != null) { + mCameraSourcePreview.release(); + mCameraSourcePreview = null; + } + if(mSoundPoolPlayer != null){ + mSoundPoolPlayer.release(); + mSoundPoolPlayer = null; + } + } +} diff --git a/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/MaterialBarcodeScannerBuilder.java b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/MaterialBarcodeScannerBuilder.java new file mode 100644 index 0000000..1d58f3e --- /dev/null +++ b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/MaterialBarcodeScannerBuilder.java @@ -0,0 +1,331 @@ +package uz.theairsoft.barcodescanner.materialbarcodescanner; + +import android.app.Activity; +import android.graphics.Color; +import android.hardware.Camera; +import android.support.annotation.NonNull; +import android.view.ViewGroup; + +import com.google.android.gms.vision.barcode.Barcode; +import com.google.android.gms.vision.barcode.BarcodeDetector; + +import org.greenrobot.eventbus.EventBus; + +import uz.theairsoft.barcodescanner.R; + +public class MaterialBarcodeScannerBuilder { + + protected Activity mActivity; + protected ViewGroup mRootView; + + protected CameraSource mCameraSource; + + protected BarcodeDetector mBarcodeDetector; + + protected boolean mUsed = false; //used to check if a builder is only used + + protected int mFacing = CameraSource.CAMERA_FACING_BACK; + protected boolean mAutoFocusEnabled = false; + + protected MaterialBarcodeScanner.OnResultListener onResultListener; + + protected int mTrackerColor = Color.parseColor("#F44336"); //Material Red 500 + + protected boolean mBleepEnabled = false; + + protected boolean mFlashEnabledByDefault = false; + + protected int mBarcodeFormats = Barcode.ALL_FORMATS; + + protected String mText = ""; + + protected int mScannerMode = MaterialBarcodeScanner.SCANNER_MODE_FREE; + + protected int mTrackerResourceID = R.drawable.material_barcode_square_512; + protected int mTrackerDetectedResourceID = R.drawable.material_barcode_square_512_green; + + /** + * Default constructor + */ + public MaterialBarcodeScannerBuilder() { + + } + + /** + * Called immediately after a barcode was scanned + * @param onResultListener + */ + public MaterialBarcodeScannerBuilder withResultListener(@NonNull MaterialBarcodeScanner.OnResultListener onResultListener){ + this.onResultListener = onResultListener; + return this; + } + + /** + * Construct a MaterialBarcodeScannerBuilder by passing the activity to use for the generation + * + * @param activity current activity which will contain the drawer + */ + public MaterialBarcodeScannerBuilder(@NonNull Activity activity) { + this.mRootView = (ViewGroup) activity.findViewById(android.R.id.content); + this.mActivity = activity; + } + + /** + * Sets the activity which will be used as the parent of the MaterialBarcodeScanner activity + * @param activity current activity which will contain the MaterialBarcodeScanner + */ + public MaterialBarcodeScannerBuilder withActivity(@NonNull Activity activity) { + this.mRootView = (ViewGroup) activity.findViewById(android.R.id.content); + this.mActivity = activity; + return this; + } + + /** + * Makes the barcode scanner use the camera facing back + */ + public MaterialBarcodeScannerBuilder withBackfacingCamera(){ + mFacing = CameraSource.CAMERA_FACING_BACK; + return this; + } + + /** + * Makes the barcode scanner use camera facing front + */ + public MaterialBarcodeScannerBuilder withFrontfacingCamera(){ + mFacing = CameraSource.CAMERA_FACING_FRONT; + return this; + } + + /** + * Either CameraSource.CAMERA_FACING_FRONT or CameraSource.CAMERA_FACING_BACK + * @param cameraFacing + */ + public MaterialBarcodeScannerBuilder withCameraFacing(int cameraFacing){ + mFacing = cameraFacing; + return this; + } + + /** + * Enables or disables auto focusing on the camera + */ + public MaterialBarcodeScannerBuilder withEnableAutoFocus(boolean enabled){ + mAutoFocusEnabled = enabled; + return this; + } + + /** + * Sets the tracker color used by the barcode scanner, By default this is Material Red 500 (#F44336). + * @param color + */ + public MaterialBarcodeScannerBuilder withTrackerColor(int color){ + mTrackerColor = color; + return this; + } + + /** + * Enables or disables a bleep sound whenever a barcode is scanned + */ + public MaterialBarcodeScannerBuilder withBleepEnabled(boolean enabled){ + mBleepEnabled = enabled; + return this; + } + + /** + * Shows a text message at the top of the barcode scanner + */ + public MaterialBarcodeScannerBuilder withText(String text){ + mText = text; + return this; + } + + /** + * Shows a text message at the top of the barcode scanner + */ + public MaterialBarcodeScannerBuilder withFlashLightEnabledByDefault(){ + mFlashEnabledByDefault = true; + return this; + } + + /** + * Bit mask (containing values like QR_CODE and so on) that selects which formats this barcode detector should recognize. + * @param barcodeFormats + * @return + */ + public MaterialBarcodeScannerBuilder withBarcodeFormats(int barcodeFormats){ + mBarcodeFormats = barcodeFormats; + return this; + } + + /** + * Enables exclusive scanning on EAN-13, EAN-8, UPC-A, UPC-E, Code-39, Code-93, Code-128, ITF and Codabar barcodes. + * @return + */ + public MaterialBarcodeScannerBuilder withOnly2DScanning() { + mBarcodeFormats = Barcode.EAN_13 | Barcode.EAN_8 | Barcode.UPC_A | Barcode.UPC_A | Barcode.UPC_E | Barcode.CODE_39 | Barcode.CODE_93 | Barcode.CODE_128 | Barcode.ITF | Barcode.CODABAR; + return this; + } + + /** + * Enables exclusive scanning on QR Code, Data Matrix, PDF-417 and Aztec barcodes. + * @return + */ + public MaterialBarcodeScannerBuilder withOnly3DScanning(){ + mBarcodeFormats = Barcode.QR_CODE | Barcode.DATA_MATRIX | Barcode.PDF417 | Barcode.AZTEC; + return this; + } + + /** + * Enables exclusive scanning on QR Codes, no other barcodes will be detected + * @return + */ + public MaterialBarcodeScannerBuilder withOnlyQRCodeScanning(){ + mBarcodeFormats = Barcode.QR_CODE; + return this; + } + + /** + * Enables the default center tracker. This tracker is always visible and turns green when a barcode is found.\n + * Please note that you can still scan a barcode outside the center tracker! This is purely a visual change. + * @return + */ + public MaterialBarcodeScannerBuilder withCenterTracker(){ + mScannerMode = MaterialBarcodeScanner.SCANNER_MODE_CENTER; + return this; + } + + /** + * Enables the center tracker with a custom drawable resource. This tracker is always visible.\n + * Please note that you can still scan a barcode outside the center tracker! This is purely a visual change. + * @param trackerResourceId a drawable resource id + * @param detectedTrackerResourceId a drawable resource id for the detected tracker state + * @return + */ + public MaterialBarcodeScannerBuilder withCenterTracker(int trackerResourceId, int detectedTrackerResourceId){ + mScannerMode = MaterialBarcodeScanner.SCANNER_MODE_CENTER; + mTrackerResourceID = trackerResourceId; + mTrackerDetectedResourceID = detectedTrackerResourceId; + return this; + } + + /** + * Build a ready to use MaterialBarcodeScanner + * + * @return A ready to use MaterialBarcodeScanner + */ + public MaterialBarcodeScanner build() { + if (mUsed) { + throw new RuntimeException("You must not reuse a MaterialBarcodeScanner builder"); + } + if (mActivity == null) { + throw new RuntimeException("Please pass an activity to the MaterialBarcodeScannerBuilder"); + } + mUsed = true; + buildMobileVisionBarcodeDetector(); + MaterialBarcodeScanner materialBarcodeScanner = new MaterialBarcodeScanner(this); + materialBarcodeScanner.setOnResultListener(onResultListener); + return materialBarcodeScanner; + } + + /** + * Build a barcode scanner using the Mobile Vision Barcode API + */ + private void buildMobileVisionBarcodeDetector() { + String focusMode = Camera.Parameters.FOCUS_MODE_FIXED; + if(mAutoFocusEnabled){ + focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE; + } + mBarcodeDetector = new BarcodeDetector.Builder(mActivity) + .setBarcodeFormats(mBarcodeFormats) + .build(); + mCameraSource = new CameraSource.Builder(mActivity, mBarcodeDetector) + .setFacing(mFacing) + .setFlashMode(mFlashEnabledByDefault ? Camera.Parameters.FLASH_MODE_TORCH : null) + .setFocusMode(focusMode) + .build(); + } + + /** + * Get the activity associated with this builder + * @return + */ + public Activity getActivity() { + return mActivity; + } + + /** + * Get the barcode detector associated with this builder + * @return + */ + public BarcodeDetector getBarcodeDetector() { + return mBarcodeDetector; + } + + /** + * Get the camera source associated with this builder + * @return + */ + public CameraSource getCameraSource() { + return mCameraSource; + } + + + /** + * Get the tracker color associated with this builder + * @return + */ + public int getTrackerColor() { + return mTrackerColor; + } + + /** + * Get the text associated with this builder + * @return + */ + public String getText() { + return mText; + } + + /** + * Get the bleep enabled value associated with this builder + * @return + */ + public boolean isBleepEnabled() { + return mBleepEnabled; + } + + /** + * Get the flash enabled by default value associated with this builder + * @return + */ + public boolean isFlashEnabledByDefault() { + return mFlashEnabledByDefault; + } + + /** + * Get the tracker detected resource id value associated with this builder + * @return + */ + public int getTrackerDetectedResourceID() { + return mTrackerDetectedResourceID; + } + + /** + * Get the tracker resource id value associated with this builder + * @return + */ + public int getTrackerResourceID() { + return mTrackerResourceID; + } + + /** + * Get the scanner mode value associated with this builder + * @return + */ + public int getScannerMode() { + return mScannerMode; + } + + public void clean() { + mActivity = null; + } +} diff --git a/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/SoundPoolPlayer.java b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/SoundPoolPlayer.java new file mode 100644 index 0000000..31d813e --- /dev/null +++ b/app/src/main/java/uz/theairsoft/barcodescanner/materialbarcodescanner/SoundPoolPlayer.java @@ -0,0 +1,29 @@ +package uz.theairsoft.barcodescanner.materialbarcodescanner; + +import android.content.Context; +import android.media.AudioManager; +import android.media.SoundPool; + +import java.util.HashMap; + +import uz.theairsoft.barcodescanner.R; + +public class SoundPoolPlayer { + + private SoundPool mShortPlayer= null; + private HashMap mSounds = new HashMap(); + + public SoundPoolPlayer(Context pContext) { + this.mShortPlayer = new SoundPool(4, AudioManager.STREAM_MUSIC, 0); + mSounds.put(R.raw.bleep, this.mShortPlayer.load(pContext, R.raw.bleep, 1)); + } + + public void playShortResource(int piResource) { + int iSoundId = (Integer) mSounds.get(piResource); + this.mShortPlayer.play(iSoundId, 0.99f, 0.99f, 0, 0, 1); + } + + public void release() { + this.mShortPlayer.release(); + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_flash_off_white_24dp.png b/app/src/main/res/drawable/ic_flash_off_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..156df5ee7f83e3223ba7da0c39d85efc559c560e GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+iWu7jMAr*{!&lz$Zau8^J$hfgX z`~%ay!>$`HzOInVzP%yrV#Dhikr zE|JP;%u`sNwV#<+w)KpgO67tli(G!&egNZNwgOEPM3@&p(K>w2FyK>)D6{JCgh|qA z!42w+K#M0Z?1{Xr-FZMTSy6?7f7*@dPb6g8qCL`t8?8TI^k*o$VUl3aBELzj+N?e? Re?HK~44$rjF6*2UngB;xQm+62 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_flash_on_white_24dp.png b/app/src/main/res/drawable/ic_flash_on_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..9e57cde14776dac481703293d0adb391c1c72a2a GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+iR8JSj5R22v2@8yW^mFn}Saq<` zgqPi;bP{u>6yJnbne3NLWE4yzWKVRruU(S)<7fO-6W-V*suQ07uk^eu;2$Mq^K<@0 z5#Im*W>1`U;`!uh+ID}ZEBHEVX8wHltUm0LNrfSg0s{jx_b2`f^RLGPEoAU?^>bP0 Hl+XkKWbHV* literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_qrcode.png b/app/src/main/res/drawable/ic_qrcode.png new file mode 100644 index 0000000000000000000000000000000000000000..d7f6d0c0f4f0570411be669890c7b4f26c529fe9 GIT binary patch literal 271 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 NRp~CWOu_u+{{iryv+w`_ literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout-land/barcode_capture.xml b/app/src/main/res/layout-land/barcode_capture.xml new file mode 100644 index 0000000..66d9179 --- /dev/null +++ b/app/src/main/res/layout-land/barcode_capture.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/actions_main.xml b/app/src/main/res/layout/actions_main.xml new file mode 100644 index 0000000..e2967fe --- /dev/null +++ b/app/src/main/res/layout/actions_main.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..5f84df1 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/barcode_capture.xml b/app/src/main/res/layout/barcode_capture.xml new file mode 100644 index 0000000..b64d8ee --- /dev/null +++ b/app/src/main/res/layout/barcode_capture.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..898f3ed59ac9f3248734a00e5902736c9367d455 GIT binary patch literal 2963 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 JPDHLkV1g@XpTGbB literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..dffca3601eba7bf5f409bdd520820e2eb5122c75 GIT binary patch literal 4905 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 lV%Z2*hu);6rydA(}wUDXPCF_W1vnaRBK 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..14ed0af35023e4f1901cf03487b6c524257b8483 GIT binary patch literal 6895 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b0907cac3bfd8fbfdc46e1108247f0a1055387ec GIT binary patch literal 6387 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..d8ae03154975f397f8ed1b84f2d4bf9783ecfa26 GIT binary patch literal 10413 zcmV;eC{ovnP){+^kJY@_qlWNt)byXXcl4&di)UgOL4U zf7l=Phy7uH*dML-fsqKMr;DlfM>yz|;&bpF`{OQzgo8jbktkySeg~64fbWuHz_H+% zO2F)JwJEE@HLSkR79_Z#oHbogc3dx%o7^AeCk{b5(&1F_9NvTf!DryJ`XFJT+JS0q z&?sCD-y=8K2W2PRhjJ3<`jzFS2UeBViE9@x1RKUQCZdv7kl1SX?3WZMS(_}*GPxT+MhW0P|fyhZ+Qq30&o zK&_A(Oze8$+U<`PdXPq;v4_f|Urm8qVAY042UnGp45})9cTiQyEh4N`WieG?WwHFJ zL%SQEJASBPNL8tfyeEVAm>Ttneh$6^dT@7TL)6K`4dZuI$Q8$@YC7*NxE8o3xHh;( z)oY%paC7#DbzBq#z7eX{hBSaAFX=&XZgM%%7vkI`tW*yCO_Yg=`yqnAa-v2eeE;?> zc{iKw z56$?22D^!CP)@={l~{!+p^?NV4J00s5s~K!m``K3Z^mK!w_^!uRBfLTqF!aWIQ-yF z+-+mFw$C)OYiVHDrh2UxX&Im_YA#t%&~JYj4^H@@?c?sN*|d{1z)fXCWK#h&a-j`x zMSwIVr!Zx+>*mUE)45>nPAFTm4uSn)0ywG_n3eP}spMCtk;WQXTc!Xa#?G<8~9?@D4_J^SH8;MHSdkm@M;{c4Zl4~|K=yFf32q2}KbIxDWFpb1y zO+OA&=Iq3=s^1(B1GFU0ED0TN)1GUEzJjf&cITr}~_843H9IFf?D zpy-;D=W+{Ha$5$7>!~TGM>3^{(aM!hTwS-Zu6}T3B@Ohtm!x|WXwD0DS$2Sg4MHki zT4wy)C@!)S)O94Q^ENX$IJLgcuiK`aOAMYnR<7i>43I*17(|~2Z^{a28-tFl06j}G z1E(L_b%g+AG(2{IghMo@X493&wrmJ$)etG%R?khj1IO;za&76!!+2C}`5mZmW7T)d zdc5TLAso7|4x4fu(6j?P@#13#aX@*#Nyh;YpF8maDO(w~k+R(hKe!7&`(pji{+WqG zRNJD}1i%xZuq*IN{U@la2#gbNVFCfAchs zIJDcO;{ZH`Z=Jz5RkkxH?-ZOri>KGuU75U|b7#sb@!GV{ltwd6tl0 z`-tj|)YKcR-o#ogdg%auyuQ|?Hi%I3R1^-|ZB z3w@dmquBHyVR{7VswXIVTX$?MPH4+9kb2qjlDK$t-RcV{VoZD69&BtHN{89>gQ~qP zJ3uX1wj2^zXGt+iUU`JHjaZ|tY;IN^;K@-L=fQS>Y@uwVEi&RUN?2Y*+sNids}(cC z+40kwrYD*P3GD#2c-goFwX_(F;ug=ctyz2p&FRs8BZP#KW)rz1wGkz3b++zpGX3NIKL+e&!v|_Kf@T~~axF4tuT$cD=XZI()UWvicEV_jFqjbw^Y;_9AkJsqs?mSQ_V zHd!_~?Uk)r`5Rg=yAOj%Y^~TwjIt7{g{Gt00kYMyk+w^ZgMfMuZBvVP>lJ}>TFiaQ z6}$vw71{x^*|Ko~^_rD(w0N!+0&330f%Q3TNHV+~AX_dQo92j#JW0ofEat`()+cpU zNK-<*Wh>c%oF}ld7(cPM7T>>P3+`N++2#S7TwjYH+FeDL-}5iew@%rhE!V8XXvx!0 zTFweF>(f3j`6XB-!?_??289+P$hL!oDad&d`knUqYw_}zU&NQL{fPhk`)_>p#vk~F zOaH-9ClAxr#e^P5nv&DV0je~`L#5{FGh$URTHx9AYn@Acj8H9 z-fn2Xa=Bbhm#_bhv)?!+_&C~>bovC&J9ipS=gMNVj42zRq^}*vKi$01ti15vyd!%p zUA9JO)5+CkcwA~i2(aSSaRpH~0l2>#}`U$mAt<;*`UUpCUF!4<_g zFf*C<$Rf;^y{H)XiCNlB=(vxmae|1Pqx`~~S}Rm0li_pUevNx<%Eh8q90Q566YDZZYFMh0VeMrAMOVe1 z|Lz;ye`{f@1!x?J0yCotz`^}fMr`Fm4fEt{bxGcZ@CDfQlmg-(RljEY}^PEkElrDm9b@vQz3{qdC=2bx32OI6ixaob7Peg<(shE$A37*Y0*ydf7hWB3l zfOPA%yE6dnF4t(NpuypoFMj$Fe(uB} zYGE`j2L$`WNWctZJGzc_^Y7cZ=&iGKe5Qp4N#!&iijDjXjTz(3xiMo>J=mmazv7G# zF};w)79FkiA@1zpCm-spe1PcGSD#bY2j6kZTSF>x2d*b>5aJ1Q0i#dXZr;STA6&qX z?AfNYN-*H~;g8?zcE?0p{`DpSKBZ+x+2NX#R$#Yh=T4y^j8P-g+?ON+%kpw5Ksi!b zOAq(oLt>AA{_iWD?hG2?wJ$%XV>2K8a2fw~=WnZlqj?=Lg8tUGU(+#}_pV&l`FXI2 z2R{CgjGSMfif5%=Dvs=1Gg5Q<1A2u%ogU0AeaR=a7WglGq9Gm z05rN_()Itp2xw&&&f%Gd_t?ff9{`jo#qQFme-Q@S8}7!~yjOSWsy>00CD&oc8BE zFMG|E_M?KjbKQ9%c|x42azM)$4)-h1zrz4(v;}}*K(PA#cWCU;R^U~Jl3;7>rw{Cu!{8QN zl(B*ZEn!VUSbEKv??13(3(hAM`|DqSwpn--f-*wJC6w9N`i?w)2q&I8VbU?i)Rp5$ zpRbmO?ySVUW0vO8F+m{!u@5;7*qFB&61$hYbWjGt9T07-U^P?#05ata{Vwd{2a}a; z(QWDK-j|R#Z<>+y4)Emu^ECb8n$m7_4%f@(9^8ck*T(DwCIkV5Cej$Fy(m5INbk)B z81_|%Sz$1T#tN3wg#Zy2eKhpDFrV~OEAFZrs~>OtfgjpaWmJ8GEc7e5$ z<-7`0<%3Bl$~A83zX=m=j13)K`E?&RU1#)%u;U-p*j;=g6-ytEUsw>Kreg^;rRu)?wAO})#2n1X6G=;eY zbpY#7JLDu;AE2T%dC;~}?3TFl3JMDHXKYCH0n`pX@o;Z)fS+3mpgvpH+sc<*x z1F}9*_-oA}DzIg@@Ei1s?3sQ04(rg@i;xN56+FJ0yx!{~|Zn%b_xqcb^P%5t(dMXW@Ug}*T&pN4~-o|+0Y3PH&pF}W=|bT0Q%e706_}svCls?Dd?;u zzf`BxSd7-LQcApTHC}%70KMPb((ph|^QvQq=sA_wK%P6L#o@{e=S=Dp9Q*VlcFK&` z3z4}2a!ZM6K#x2yjjU$pQYbW-n|+%|^QNhAEZ%^{+o;|Dp_Dctk{ReEnaG1N7!M zUvln?NB+f`^cqb${^jex;SpPlIV(gVl3I2ghz8NCZ=kUwM+yh%k@0;{mh_r60fM<7 zQyUMG(-U4kq8@)Rcpf7Gs5P<|e4I7+Y4)N_=QfSdz}A0i8M z<9|WJh7HjV5X(eFBM0>$=J8u=0pwnoia*!0$bca|pm_&(<4!rrxI=n8_RLDeAtY}2 z=*KHo>(0ZuLTbvfXLb_qK-^8I+%| zUdG%Cl=sFd>;Oyj@<24U&RhVc(aBVo=p`QzCVUthI@4N3$j=WxTE)7Iqpe%ok|sRnzE-FFFLy4v@Ojy zAh^N;M6&#AA&{i2o>0u#PM074u4E9~0hJ6dw^~A0!+7s~xzzXy*t&$}*`nH~ad24Swg^YQW%SiNd)(;TZ&v!xo_w?$uA?IrfP_|`m zEQFQk^)0w$mv+7L-8Z=N`c!^^cB=rCZUjVG+>M2OQ>B-YZ>N5giD0_7nBKcn9Z(nY zVT8K$EKGZqvp|-)wRvDgk=|8G?b5E#u3g0gVLJp(fT}bAG6o{JwYgv&4v1g=CLIIv zMIDs;tm=7)QDC4e`P->SW@4!&?~R8=%fD+wwQ%fNlz;`*m_7f4lZg zPs+CxK;6mf8GGySjQUzZnze5S&OQAymYz5)_&eH^bn*y2)>B%~UnfXQkL<$*XJ5rj zUfj!-MX2_vYu16CIG-E`Qa)zv+b&q$i!-$Vw2cR#ICW+4KtvPw2|#OCVb?j+tDrN5 z?)7#T8bCM2K|x)hC)UY#!K_emE(FoWtx~UdHXaJ8k-wu&kn8+J-4;A-Q@)_j>(YJY zg?Mu97A%3iAvFK5B_WJYJ=Uk;DLX5%Z$S!1DXUc!tzD^_ios5qQXIOg3I}f~YCb`# zRk6GpUA2J+pg4XtgGkD)Rv#BBbDlJQ4i`ZC2o9iC;vkyV;Ys8tPL2MM0+eN;g~p)} z0w6LgK%2DyWB@z>N{>Q5fDD62D?moT1F($VrU{S^crr8~0`~=JA&cjHO4_~;Wq@Nr zWEemQNj!S?^ny4@yn0cIMFA2Bk;MTr5FUPj42OpoAS2;v4v+wNsNimoCijJ&noYkkmt8oOdws$f#{!w*f?U)Jch8E3A=KN%$ z+~TWqXo1Kw0L2&$j}jo#@V*79M#G~7Xtyqagu%lBw2>bmUGSvS8y4j#ei=rgkL1%f z@7Ap&y`32$qxTGRKt41A?~MHXhN9HfKQK2YxA^)%Jnqcg06k8QB}t7j8Xmm>352H! zplw$Td3)1=B;S71raVS|C4XCE+i!)Y)YsxC zwr{1D2jEFPc?7RGyqCV#udVzd$BRCC0H?lu6o-;y!s{o=UxTz0REZZH+>J9|JAt3s zzmvYE+Eq#889~}zMJ*4&lX>bSjy`sXzE)_;9zIn!*Yltns(4batkeI%Q%T*?_v-l- zwzrm3eQo2^eRVjbFzZgQkn!Qr)?Qv-9>(^*n!7QC+Pie_+=cw@9hkfB2xJx-vh}yA zTVn@TmEvJ#1=R8YJWubbp>9m4%JS)VG&LMlUV!KB-HunhxDSsc$As6z%h&U3vo;k{ zO$HcWI*2C`VCj2X3Q12&RYlshwMk%k0G`!-Fx?$J^uSaSsW%wXr8mn$ z;~AVgF)0R8iD^b{(GvruXp?%J)1xrGDF!ki=FyCE)MFsSVjfM6Au&)Wu}Bi=^k|QH z6l$achszhr(CFcFXd8EPGdXzH1jvCdyxFM(++21qTCwm28srMxgw9+m)jJWN4erJ$ zfHVLZMJ&MMe#UxB{gzxExlj?R><7D^?>gd zIsvP#Th0rRf$)HO7NyhMYMKBt93Bp!1R5YW1IR#lv;!2+Z+#M@Fq;1OKH8?<-rZ>% zn<;qKH8R~3_2@bhB`p7*PXFr}owme&VS;Ayb&TsY1IP$?02pEJib{@y9PbYJ9-F0^9DWM#x0cd9E8d{Nhwu7<=K>8+N^$ZNE0c0dR zf&mgRx77?FBjITdP&~i&$sz#7EWzl}kQ~~U7Pda>u@Fr0w?{q5-~J?^euK+yOKh+@ zK-wS@FtV&4AYl`uO#r1C4No(GOn|2epc(>Df)>{$ZJ_HW%?-am+He4COHWJ0KH7U^ zJ}zBh%m57^@+5I(e{q>?{I1NR0BKHp2%Oha0+beGG(36%GGJC+2~b6`N$@BEs@DQg zX1pBgOSE*}Efmy$I&DJ>^}KXhp?36ES5Hqr^0%LO&a^z*cv>b}Ee=pNt0)6z*0lp< zSV{&gYQPJSfhidrK-D||#TlBCfycn$tyX}D>xy2C#ZNx60osnWp*w3+F|xu#VTHJL zgq)pW3H*WRxp}YA%HipiSp^_NAR?fQ+R6uz;rTqg02z_b!w-<*@IW1C1t<%~d{$u5 ztf~K`ZN{~oH)~6)SfAzrbq8wx0#N79V@ObTnO>*{L{8A*)}e#1H3DaS0kwz1l{q{-VIh)6$u;94s{*9U z5~XMZ$oNb`HGoXWBy0kx#3Xo{0hGz&9?~NdEngrPj~y9BU6+T4KW#fJ1kU3zQ!wON-a=10NQ87wwb%6LRQHnNzVok~O}hUVsF`(;T3r*TuC}N0kXv5o)1FlPiM+Bqt}hut8}4Q~S}Hl}cCEA^@pEl%fTo9TnOE z5;!qR0U`~r9Ux&7qZFX$wE$!QJWT-AasYwrihB-=rayj^whh-tom(<6q$B9d zZUq^P7R@|EduBNavK9kK0a0o+4?xA*0Wx4#9hQ{S4v_F!bx8Vx+?{3s83>O8AUKu; z7R5-2!lIdB=SZ6jp>5M1b)#+7g073t3W?bexF?D1dr=>Y&`=aP=RG=KRF>NSOQy95 zK)et|<53k_05UKoLpwl*rDX5|WCT1=*3s1jpuM#X5*RF;GwnaH88>Ycu5CP3rYl6q zMjop1khimkM{gLVb|XErK`9BJ!`9JjPoHdbLU(bm z;eEj(uqd?P&>oz1`XpVG5SEpLMGg41O+(c*@m(RvVTLqR$Rvb$EPmC{;Fw=5eU(@q zfM-E*{{K4m?)@;dfs>DWA9{;2*ESMcghxGlkqgj#6g@N7fPjz(bJITSk)MJkc}X&3 zx1n||Scj*RSZZ`#x$)as6IUTgi=&nY;DLm932`IpiqozPb@`WM;c2AddJtCz%c<}x zlTT7LK>|GFFhd$DOoH+&LAOZEBO#raL9xrfVDKn#VxV-BG6@wi5acWy8uM^nb<*3C zF2kbP(>^3_>j4H&AJ*e?wdPcXIU#bR%Y(SN^(B7;+qG*q9Lts!hUfDDKvSRB0+0c->J*@QZ2-mV0!U8Bd1526=;cl}bkQ8tzni+Ng#wO^Uu3(L_tPcUJ2^F{|sY8r}6)1CKU{y0Ag40i>Wq#8V$DMynRd zXk`mr#M7(*DR#7h*J;LQ680?4Yz~kS`8@mp>4Aq_pJ?eknRs%@Ca6=I+r!mym(~ss zA4IM+m~%${$kj2BJP&es;J(Eua`v~}s5PX5=yquq0SGoEfnRZ&amirK05UQetT{mO z+VYs?G@CFn3XA4Hby++zco~HU>eLzaW&yLSEe#Z!GbVCj-N~NF)fFHbEb;NWAI%Ow z1wNeH15|rvqs0JH3^oD)2Bu^v0V+y2DU+}Xpi&+1NE_($Rg19bsnD~MPM#C!sK1x% zAX=wf-MX~Km`A83YRASRU?Q&vfoLGi&p=!xesa=!(en8>x#^F@M!Hf~mK6a~LS$G< zhHij_&#Ef{sw!;`4kW-spbWV@OXl1ZKNeC#V@a6X;(mxdSet;y4)0u*1N9VQ6mnIhyQEZyBO%Gb%x{I6!oXH>p9h>Ks5dJOCM%k^un0ed6UHP%Pb8m@^LR*1I5nOkq_hdUc^+S%FHIjIFJs_SQx=R!_ z{|}V3f?1%o4b%2-m&4)?76nK(Cekx8+8iL`lEGk!m8tc$a$f-|$Uu0~PAo}G2sF?{mwdqxbK&cGQ$%gni}UaT%W z>{iFH*vN(TF1pf6baWg*dmhXpN!;AVi65PqEqZ491+;wOpOAS+8#RZ)#91aeU3opr zM1U0TES(RaEFAz5U^3zeEO9c{qvEDbq@;7OZ2q63IpG(?4?U1W%5uNL;yAjv45nq} z!0F2Bz~yd^b&Rz}5@xDhSt1nNKIG>}ewB_*u5Bn$utQM)S>h>^Dn$#P{*b_Qi}v2A zWlB&7DvMeu3e}jpavVlt4oQvyTVrcNloqGbjn8N#ujME$ULBYWcGoQFO`)jyw?y-1 zd?*fmxYA*8|JiWuY&?g$Do4)Z__4Bjv$8v>bkFVZm;oftBGK_9@@pl%lXjej!A!LC zh#}9ohCi{{ZQ-mp-B&KY>P}({57N+{xyjh8FctPfr+T!$Mn30oz09XHQwIB^dljb1 z$^SVOsXW(wZ+)uVGjE;TvtW(PvtX@k@RmZ^+(Uch12(V6o&_nG{11DO9u@4h`w=yp@yLR7+-F_P_1>{dzv%Vc z{4?EWO|R#D_cC>41Q@6rEpfZPY}Qsw(iu+VtM zk?VfLxt-`8D*o)6RH0G0sdlU^c5qq%Bu%TN3R6ec{q<$PcmS#o?ctDy1vk>p({m{8 zE>kOk6c$U>a;ZxBKlm)ODnpQ`%TPxJEO2ZmdS9GBJEt$ZhK?H0Xj&UPI5rAX2R88L z$%0cK7N~Y(7NHkw?B3M1K;whO01!A0WE#NW=*IvFVBhg)$LPV1*_EBco1N2*U4tE( zRtl2?YqWMOIBn0yR9sp7qyVcUb1gnBpzXq7P*oT9KOgqljw+zIvtzojb2zbcN;KS) z9hz1SlqysTupC)~JF~`b&#VTY6#sW--*Hp{MHLo1Fn0-5nsA9VKvNapXEcv<*FF9Z XdJ+W}DiIkV00000NkvXXu0mjfKBlg6 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..2c18de9e66108411737e910f5c1972476f03ddbf GIT binary patch literal 9128 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 I20H400LcNok^lez literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..beed3cdd2c32af5114a7dc70b9ef5b698eb8797e GIT binary patch literal 15132 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/raw/bleep.wav b/app/src/main/res/raw/bleep.wav new file mode 100644 index 0000000000000000000000000000000000000000..4daff85681f43dc84df352bf5662a3b05a05f167 GIT binary patch literal 47104 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 K<$wQQ`u_)fIABEp literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..fc15aa1 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #7F000000 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..c1349db --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + MaterialBarcodeScanner + Access to the camera is needed for barcode detection. + Cannot start the barcode reader because the camera permission was not granted. + BarcodeScanner + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..457e90a --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,14 @@ + + + + + +