├── .gitignore ├── LICENSE ├── README.md ├── RxQrCode ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── piasy │ │ └── rxqrcode │ │ ├── ImageFrame.java │ │ ├── RxQrCode.java │ │ └── view │ │ ├── HoleContainer.java │ │ ├── HoleView.java │ │ └── WeChatQrScannerView.java │ └── res │ └── values │ └── attrs.xml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── piasy │ │ └── rxqrcode │ │ └── example │ │ ├── MainActivity.java │ │ ├── QrScanActivity.java │ │ ├── QrScanFragment.java │ │ └── UriUtil.java │ └── res │ ├── drawable-xhdpi │ ├── moving_bar.png │ └── side_bg.9.png │ ├── drawable │ ├── btn_scanner_pick_photo_bg_selector.xml │ ├── button_10black_bg.xml │ └── button_5black_bg.xml │ ├── layout │ ├── activity_main.xml │ └── fragment_qr_scan.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── art └── screenshot.jpg ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | **.iml 2 | .idea 3 | .gradle 4 | /local.properties 5 | .DS_Store 6 | **/build 7 | /captures 8 | 9 | /bintray.properties 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Piasy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RxQrCode 2 | 3 | When QrCode meets RxJava... 4 | 5 | [ ![Download](https://api.bintray.com/packages/piasy/maven/RxQrCode/images/download.svg) ](https://bintray.com/piasy/maven/RxQrCode/_latestVersion) 6 | 7 | ![screenshot](art/screenshot.jpg) 8 | 9 | ## Usage 10 | 11 | ### dependency 12 | 13 | ``` gradle 14 | allprojects { 15 | repositories { 16 | maven { 17 | url "http://dl.bintray.com/piasy/maven" 18 | } 19 | } 20 | } 21 | 22 | compile 'com.github.piasy:RxQrCode:1.3.0' 23 | ``` 24 | 25 | ### initialize 26 | 27 | Because RxQrCode use [CameraCompat](https://github.com/Piasy/CameraCompat) for camera scan, you need initialize it: 28 | 29 | ``` java 30 | // initialize in application's onCreate 31 | CameraCompat.init(getApplicationContext()); 32 | ``` 33 | 34 | ### prebuilt scanner view 35 | 36 | Also because RxQrCode use [CameraCompat](https://github.com/Piasy/CameraCompat) for camera scan, you need add a preview container ViewGroup. 37 | 38 | ``` xml 39 | 44 | 45 | 50 | 62 | 63 | ``` 64 | 65 | ### scan from camera 66 | 67 | Call `RxQrCode.scanFromCamera` from your `onCreate` of Activity/Fragment. 68 | 69 | **Note that you must add** `getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)` in your Activity's `onCreate`, otherwise the camera preview may not show. 70 | 71 | ~~~ java 72 | @Override 73 | public void onCreate(Bundle savedInstanceState) { 74 | super.onCreate(savedInstanceState); 75 | 76 | RxQrCode.scanFromCamera(savedInstanceState, getActivity().getSupportFragmentManager(), 77 | R.id.mScannerPreview, this) 78 | .observeOn(AndroidSchedulers.mainThread()) 79 | .subscribe(result -> { 80 | // you got the scan result 81 | }, e -> { 82 | // other error happened, **code not found won't get there** 83 | }); 84 | } 85 | ~~~ 86 | 87 | ### scan from picture 88 | 89 | ``` java 90 | RxQrCode.scanFromPicture(realPath) 91 | .subscribeOn(Schedulers.io()) 92 | .observeOn(AndroidSchedulers.mainThread()) 93 | .subscribe(result -> { 94 | // you got the result 95 | }, e -> { 96 | Toast.makeText(getContext(), "code not found", 97 | Toast.LENGTH_SHORT).show(); 98 | }); 99 | ``` 100 | 101 | ### generate qr code 102 | 103 | ``` java 104 | RxQrCode.generateQrCodeFile(getContext(), "piasy", 200, 200) 105 | .subscribeOn(Schedulers.computation()) 106 | .subscribe(file -> { 107 | // you got the qr code image file 108 | }); 109 | ``` 110 | 111 | ### scan from camera backpressure problem 112 | 113 | Note that scan result from camera will be emitted continuously, you need handle backpressure issue currently, please refer to [full example here](https://github.com/Piasy/RxQrCode/blob/master/app/src/main/java/com/github/piasy/rxqrcode/example/QrScanFragment.java). 114 | 115 | ## Try demo app 116 | 117 | Demo app can be downloaded from https://fir.im/RQR . Thanks for fir.im! 118 | -------------------------------------------------------------------------------- /RxQrCode/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /RxQrCode/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | apply plugin: 'com.android.library' 26 | apply plugin: 'me.tatarka.retrolambda' 27 | apply from: "https://raw.githubusercontent.com/Piasy/BintrayUploadScript/master/bintray.gradle" 28 | 29 | android { 30 | compileSdkVersion rootProject.ext.androidCompileSdkVersion 31 | buildToolsVersion rootProject.ext.androidBuildToolsVersion 32 | 33 | defaultConfig { 34 | minSdkVersion rootProject.ext.minSdkVersion 35 | targetSdkVersion rootProject.ext.targetSdkVersion 36 | versionCode rootProject.ext.releaseVersionCode 37 | versionName rootProject.ext.releaseVersionName 38 | } 39 | buildTypes { 40 | release { 41 | minifyEnabled false 42 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 43 | } 44 | } 45 | 46 | compileOptions { 47 | sourceCompatibility JavaVersion.VERSION_1_8 48 | targetCompatibility JavaVersion.VERSION_1_8 49 | } 50 | } 51 | 52 | dependencies { 53 | compile 'com.github.piasy:CameraCompat:1.3.0' 54 | compile 'com.google.zxing:core:3.3.0' 55 | compile "io.reactivex:rxjava:${rootProject.ext.rxJavaVersion}" 56 | } 57 | -------------------------------------------------------------------------------- /RxQrCode/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/piasy/tools/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /RxQrCode/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /RxQrCode/src/main/java/com/github/piasy/rxqrcode/ImageFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.rxqrcode; 26 | 27 | /** 28 | * Created by Piasy{github.com/Piasy} on 19/10/2016. 29 | */ 30 | class ImageFrame { 31 | private final byte[] mData; 32 | private final int mWidth; 33 | private final int mHeight; 34 | 35 | ImageFrame(byte[] data, int width, int height, boolean rotate) { 36 | if (rotate) { 37 | byte[] rotatedData = new byte[data.length]; 38 | for (int y = 0; y < height; y++) { 39 | for (int x = 0; x < width; x++) { 40 | rotatedData[x * height + height - y - 1] = data[x + y * width]; 41 | } 42 | } 43 | int tmp = width; 44 | width = height; 45 | height = tmp; 46 | data = rotatedData; 47 | } 48 | this.mData = data; 49 | this.mWidth = width; 50 | this.mHeight = height; 51 | } 52 | 53 | byte[] getData() { 54 | return mData; 55 | } 56 | 57 | int getWidth() { 58 | return mWidth; 59 | } 60 | 61 | int getHeight() { 62 | return mHeight; 63 | } 64 | 65 | ImageFrame deepCopy() { 66 | byte[] data = new byte[mData.length]; 67 | System.arraycopy(mData, 0, data, 0, mData.length); 68 | return new ImageFrame(data, mWidth, mHeight, false); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /RxQrCode/src/main/java/com/github/piasy/rxqrcode/RxQrCode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.rxqrcode; 26 | 27 | import android.content.Context; 28 | import android.graphics.Bitmap; 29 | import android.graphics.BitmapFactory; 30 | import android.graphics.Color; 31 | import android.os.Bundle; 32 | import android.os.Environment; 33 | import android.support.annotation.IdRes; 34 | import android.support.annotation.Nullable; 35 | import android.support.v4.app.FragmentManager; 36 | import com.github.piasy.cameracompat.CameraCompat; 37 | import com.google.zxing.BarcodeFormat; 38 | import com.google.zxing.BinaryBitmap; 39 | import com.google.zxing.ChecksumException; 40 | import com.google.zxing.DecodeHintType; 41 | import com.google.zxing.EncodeHintType; 42 | import com.google.zxing.FormatException; 43 | import com.google.zxing.LuminanceSource; 44 | import com.google.zxing.MultiFormatWriter; 45 | import com.google.zxing.NotFoundException; 46 | import com.google.zxing.PlanarYUVLuminanceSource; 47 | import com.google.zxing.RGBLuminanceSource; 48 | import com.google.zxing.Result; 49 | import com.google.zxing.WriterException; 50 | import com.google.zxing.common.BitMatrix; 51 | import com.google.zxing.common.HybridBinarizer; 52 | import com.google.zxing.qrcode.QRCodeReader; 53 | import java.io.File; 54 | import java.io.FileOutputStream; 55 | import java.io.IOException; 56 | import java.util.Collections; 57 | import java.util.Map; 58 | import rx.Emitter; 59 | import rx.Observable; 60 | import rx.schedulers.Schedulers; 61 | 62 | /** 63 | * Created by Piasy{github.com/Piasy} on 19/10/2016. 64 | */ 65 | 66 | public final class RxQrCode { 67 | private static final int QR_CODE_LENGTH = 200; 68 | private static final int QR_CODE_IN_SAMPLE_LENGTH = 512; 69 | private static final int MAX_RETRY_TIMES = 4; 70 | 71 | private static final Map TRY_HARDER = Collections 72 | .singletonMap(DecodeHintType.TRY_HARDER, true); 73 | 74 | private RxQrCode() { 75 | // no instance 76 | } 77 | 78 | public static Observable scanFromCamera(@Nullable Bundle savedInstanceState, 79 | FragmentManager fragmentManager, @IdRes final int container, 80 | CameraCompat.ErrorHandler handler) { 81 | return create(savedInstanceState, fragmentManager, container, handler) 82 | .observeOn(Schedulers.computation()) 83 | .map(ImageFrame::deepCopy) 84 | .map(RxQrCode::frame2source) 85 | .flatMap(source -> resolve(source, false)); 86 | } 87 | 88 | public static Observable scanFromPicture(String path) { 89 | return Observable.fromCallable(() -> { 90 | final BitmapFactory.Options options = new BitmapFactory.Options(); 91 | options.inJustDecodeBounds = true; 92 | BitmapFactory.decodeFile(path, options); 93 | 94 | options.inSampleSize = calculateInSampleSize(options, QR_CODE_IN_SAMPLE_LENGTH, 95 | QR_CODE_IN_SAMPLE_LENGTH); 96 | 97 | options.inJustDecodeBounds = false; 98 | int[] pixels = null; 99 | 100 | QRCodeReader reader = new QRCodeReader(); 101 | int retryTimes = 0; 102 | Result result = null; 103 | while (result == null && retryTimes < MAX_RETRY_TIMES) { 104 | Bitmap picture = BitmapFactory.decodeFile(path, options); 105 | int width = picture.getWidth(); 106 | int height = picture.getHeight(); 107 | if (pixels == null) { 108 | pixels = new int[picture.getWidth() * picture.getHeight()]; 109 | } 110 | picture.getPixels(pixels, 0, width, 0, 0, width, height); 111 | picture.recycle(); 112 | LuminanceSource source = new RGBLuminanceSource(width, height, pixels); 113 | BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); 114 | try { 115 | result = reader.decode(bitmap, TRY_HARDER); 116 | } catch (NotFoundException | ChecksumException | FormatException ignored) { 117 | retryTimes++; 118 | options.inSampleSize *= 2; 119 | } 120 | } 121 | reader.reset(); 122 | 123 | return result; 124 | }).flatMap(result -> { 125 | if (result == null) { 126 | return Observable.error(NotFoundException.getNotFoundInstance()); 127 | } 128 | return Observable.just(result); 129 | }); 130 | } 131 | 132 | /** 133 | * @Deprecated use {@link #generateQrCodeFile(Context, String, int, int)} to avoid bitmap 134 | * management. 135 | */ 136 | @Deprecated 137 | public static Observable generateQrCode(String content, int width, int height) { 138 | return Observable.fromEmitter(emitter -> { 139 | MultiFormatWriter writer = new MultiFormatWriter(); 140 | try { 141 | BitMatrix bm = writer.encode(content, BarcodeFormat.QR_CODE, QR_CODE_LENGTH, 142 | QR_CODE_LENGTH, Collections.singletonMap(EncodeHintType.MARGIN, 0)); 143 | Bitmap bitmap = Bitmap.createBitmap(QR_CODE_LENGTH, QR_CODE_LENGTH, 144 | Bitmap.Config.ARGB_8888); 145 | 146 | for (int i = 0; i < QR_CODE_LENGTH; i++) { 147 | for (int j = 0; j < QR_CODE_LENGTH; j++) { 148 | bitmap.setPixel(i, j, bm.get(i, j) ? Color.BLACK : Color.WHITE); 149 | } 150 | } 151 | emitter.onNext(Bitmap.createScaledBitmap(bitmap, width, height, true)); 152 | emitter.onCompleted(); 153 | } catch (WriterException e) { 154 | emitter.onError(e); 155 | } 156 | }, Emitter.BackpressureMode.BUFFER); 157 | } 158 | 159 | public static Observable generateQrCodeFile(Context context, String content, int width, 160 | int height) { 161 | return Observable.fromEmitter(emitter -> { 162 | MultiFormatWriter writer = new MultiFormatWriter(); 163 | Bitmap origin = null; 164 | Bitmap scaled = null; 165 | try { 166 | BitMatrix bm = writer.encode(content, BarcodeFormat.QR_CODE, QR_CODE_LENGTH, 167 | QR_CODE_LENGTH, Collections.singletonMap(EncodeHintType.MARGIN, 0)); 168 | origin = Bitmap.createBitmap(QR_CODE_LENGTH, QR_CODE_LENGTH, 169 | Bitmap.Config.ARGB_8888); 170 | 171 | for (int i = 0; i < QR_CODE_LENGTH; i++) { 172 | for (int j = 0; j < QR_CODE_LENGTH; j++) { 173 | origin.setPixel(i, j, bm.get(i, j) ? Color.BLACK : Color.WHITE); 174 | } 175 | } 176 | scaled = Bitmap.createScaledBitmap(origin, width, height, true); 177 | File dir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES); 178 | if (dir == null) { 179 | emitter.onError(new IllegalStateException("external file system unavailable!")); 180 | return; 181 | } 182 | String fileName = "rx_qr_" + System.currentTimeMillis() + ".png"; 183 | File localFile = new File(dir, fileName); 184 | 185 | FileOutputStream outputStream = new FileOutputStream(localFile); 186 | scaled.compress(Bitmap.CompressFormat.PNG, 85, outputStream); 187 | outputStream.flush(); 188 | outputStream.close(); 189 | 190 | emitter.onNext(localFile); 191 | emitter.onCompleted(); 192 | } catch (WriterException | IOException e) { 193 | emitter.onError(e); 194 | } finally { 195 | if (origin != null) { 196 | origin.recycle(); 197 | } 198 | if (scaled != null) { 199 | scaled.recycle(); 200 | } 201 | } 202 | }, Emitter.BackpressureMode.BUFFER); 203 | } 204 | 205 | private static Observable create(@Nullable Bundle savedInstanceState, 206 | FragmentManager fragmentManager, @IdRes final int container, 207 | CameraCompat.ErrorHandler handler) { 208 | return Observable.fromEmitter(emitter -> { 209 | new CameraCompat.Builder( 210 | new CameraCompat.VideoCaptureCallback() { 211 | @Override 212 | public void onVideoSizeChanged(int width, int height) { 213 | } 214 | 215 | @Override 216 | public void onFrameData(byte[] data, int width, int height) { 217 | emitter.onNext(new ImageFrame(data, width, height, false)); 218 | } 219 | }, handler) 220 | .frontCamera(false) 221 | .build() 222 | .startPreview(savedInstanceState, fragmentManager, container); 223 | }, Emitter.BackpressureMode.DROP); 224 | } 225 | 226 | private static LuminanceSource frame2source(ImageFrame frame) { 227 | return new PlanarYUVLuminanceSource(frame.getData(), 228 | frame.getWidth(), frame.getHeight(), 0, 0, frame.getWidth(), 229 | frame.getHeight(), false); 230 | } 231 | 232 | private static Observable resolve(LuminanceSource source, boolean failWhenNotFound) { 233 | BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); 234 | QRCodeReader reader = new QRCodeReader(); 235 | try { 236 | return Observable.just(reader.decode(bitmap, TRY_HARDER)); 237 | } catch (NotFoundException | ChecksumException | FormatException e) { 238 | if (failWhenNotFound) { 239 | return Observable.error(e); 240 | } 241 | } finally { 242 | reader.reset(); 243 | } 244 | return Observable.empty(); 245 | } 246 | 247 | private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, 248 | int reqHeight) { 249 | final int height = options.outHeight; 250 | final int width = options.outWidth; 251 | int inSampleSize = 1; 252 | 253 | while (height / inSampleSize > reqHeight && width / inSampleSize > reqWidth) { 254 | inSampleSize *= 2; 255 | } 256 | 257 | return inSampleSize; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /RxQrCode/src/main/java/com/github/piasy/rxqrcode/view/HoleContainer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.rxqrcode.view; 26 | 27 | import android.content.Context; 28 | import android.content.res.TypedArray; 29 | import android.util.AttributeSet; 30 | import android.view.View; 31 | import android.view.ViewGroup; 32 | import android.widget.FrameLayout; 33 | import com.github.piasy.cameracompat.rxqrcode.R; 34 | 35 | /** 36 | * Created by Piasy{github.com/Piasy} on 19/10/2016. 37 | */ 38 | 39 | public class HoleContainer extends FrameLayout { 40 | private HoleView mHoleView; 41 | 42 | private boolean mInitialized = false; 43 | private int mOutsideColor; 44 | 45 | public HoleContainer(Context context) { 46 | this(context, null); 47 | } 48 | 49 | public HoleContainer(Context context, AttributeSet attrs) { 50 | this(context, attrs, 0); 51 | } 52 | 53 | public HoleContainer(Context context, AttributeSet attrs, int defStyleAttr) { 54 | super(context, attrs, defStyleAttr); 55 | 56 | TypedArray a = context.getTheme() 57 | .obtainStyledAttributes(attrs, R.styleable.HoleContainer, 0, 0); 58 | mOutsideColor = a.getColor(R.styleable.HoleContainer_outside_color, 0x80000000); 59 | a.recycle(); 60 | } 61 | 62 | public void setOutsideColor(int outsideColor) { 63 | mOutsideColor = outsideColor; 64 | mHoleView.setColor(mOutsideColor); 65 | } 66 | 67 | @Override 68 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 69 | super.onLayout(changed, left, top, right, bottom); 70 | 71 | if (!mInitialized) { 72 | if (getChildCount() != 1) { 73 | throw new IllegalStateException("HoleContainer must have exactly one child!"); 74 | } 75 | mHoleView = new HoleView(getContext()); 76 | LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 77 | ViewGroup.LayoutParams.MATCH_PARENT); 78 | mHoleView.setLayoutParams(params); 79 | addView(mHoleView); 80 | View child = getChildAt(0); 81 | mHoleView.setHole(mOutsideColor, child.getX(), child.getY(), child.getMeasuredWidth(), 82 | child.getMeasuredHeight()); 83 | 84 | mInitialized = true; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /RxQrCode/src/main/java/com/github/piasy/rxqrcode/view/HoleView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.rxqrcode.view; 26 | 27 | import android.content.Context; 28 | import android.graphics.Canvas; 29 | import android.graphics.Paint; 30 | import android.graphics.Path; 31 | import android.util.AttributeSet; 32 | import android.view.View; 33 | 34 | /** 35 | * Created by Piasy{github.com/Piasy} on 19/10/2016. 36 | */ 37 | 38 | public class HoleView extends View { 39 | 40 | private final Paint mPaint = new Paint(); 41 | private final Path mPath = new Path(); 42 | private boolean mInitialized = false; 43 | 44 | private float mHoleX; 45 | private float mHoleY; 46 | private int mHoleWidth; 47 | private int mHoleHeight; 48 | 49 | public HoleView(Context context) { 50 | this(context, null); 51 | } 52 | 53 | public HoleView(Context context, AttributeSet attrs) { 54 | this(context, attrs, 0); 55 | } 56 | 57 | public HoleView(Context context, AttributeSet attrs, int defStyleAttr) { 58 | super(context, attrs, defStyleAttr); 59 | 60 | mPaint.setStyle(Paint.Style.FILL); 61 | mPaint.setAntiAlias(true); 62 | setLayerType(LAYER_TYPE_HARDWARE, mPaint); 63 | } 64 | 65 | public void setHole(int color, float holeX, float holeY, int holeWidth, int holeHeight) { 66 | mInitialized = true; 67 | 68 | mPaint.setColor(color); 69 | mHoleX = holeX; 70 | mHoleY = holeY; 71 | mHoleWidth = holeWidth; 72 | mHoleHeight = holeHeight; 73 | invalidate(); 74 | } 75 | 76 | public void setColor(int color) { 77 | mPaint.setColor(color); 78 | invalidate(); 79 | } 80 | 81 | @Override 82 | protected void onDraw(Canvas canvas) { 83 | super.onDraw(canvas); 84 | 85 | if (!mInitialized) { 86 | return; 87 | } 88 | 89 | int width = getWidth(); 90 | int height = getHeight(); 91 | 92 | mPath.reset(); 93 | mPath.moveTo(0, 0); 94 | mPath.lineTo(width, 0); 95 | mPath.lineTo(width, mHoleY); 96 | mPath.lineTo(0, mHoleY); 97 | mPath.lineTo(0, 0); 98 | canvas.drawPath(mPath, mPaint); 99 | 100 | mPath.reset(); 101 | mPath.moveTo(0, mHoleY); 102 | mPath.lineTo(mHoleX, mHoleY); 103 | mPath.lineTo(mHoleX, mHoleY + mHoleHeight); 104 | mPath.lineTo(0, mHoleY + mHoleHeight); 105 | mPath.lineTo(0, mHoleY); 106 | canvas.drawPath(mPath, mPaint); 107 | 108 | mPath.reset(); 109 | mPath.moveTo(mHoleX + mHoleWidth, mHoleY); 110 | mPath.lineTo(width, mHoleY); 111 | mPath.lineTo(width, mHoleY + mHoleHeight); 112 | mPath.lineTo(mHoleX + mHoleWidth, mHoleY + mHoleHeight); 113 | mPath.lineTo(mHoleX + mHoleWidth, mHoleY); 114 | canvas.drawPath(mPath, mPaint); 115 | 116 | mPath.reset(); 117 | mPath.moveTo(0, mHoleY + mHoleHeight); 118 | mPath.lineTo(width, mHoleY + mHoleHeight); 119 | mPath.lineTo(width, height); 120 | mPath.lineTo(0, height); 121 | mPath.lineTo(0, mHoleY + mHoleHeight); 122 | canvas.drawPath(mPath, mPaint); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /RxQrCode/src/main/java/com/github/piasy/rxqrcode/view/WeChatQrScannerView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.rxqrcode.view; 26 | 27 | import android.content.Context; 28 | import android.content.res.TypedArray; 29 | import android.support.annotation.DrawableRes; 30 | import android.util.AttributeSet; 31 | import android.view.Gravity; 32 | import android.view.ViewGroup; 33 | import android.widget.FrameLayout; 34 | import android.widget.ImageView; 35 | import com.github.piasy.cameracompat.rxqrcode.R; 36 | 37 | /** 38 | * Created by Piasy{github.com/Piasy} on 19/10/2016. 39 | */ 40 | 41 | public class WeChatQrScannerView extends FrameLayout { 42 | private static final int MOVING_INTERVAL = 16; 43 | 44 | private final ImageView mMovingBar; 45 | private int mBarMargin; 46 | private int mMovingSpeed; 47 | 48 | private int mHeight; 49 | private int mCurPos; 50 | 51 | private final Runnable mMovingTask = new Runnable() { 52 | @Override 53 | public void run() { 54 | if (mHeight == 0) { 55 | mHeight = getHeight(); 56 | } 57 | if (mHeight != 0) { 58 | mCurPos += mMovingSpeed; 59 | if (mCurPos >= mHeight - mBarMargin) { 60 | mCurPos = mBarMargin; 61 | } 62 | LayoutParams params = (LayoutParams) mMovingBar.getLayoutParams(); 63 | params.topMargin = mCurPos; 64 | mMovingBar.setLayoutParams(params); 65 | } 66 | 67 | postDelayed(this, MOVING_INTERVAL); 68 | } 69 | }; 70 | 71 | public WeChatQrScannerView(Context context) { 72 | this(context, null, 0); 73 | } 74 | 75 | public WeChatQrScannerView(Context context, AttributeSet attrs) { 76 | this(context, attrs, 0); 77 | } 78 | 79 | public WeChatQrScannerView(Context context, AttributeSet attrs, int defStyleAttr) { 80 | super(context, attrs, defStyleAttr); 81 | 82 | TypedArray a = context.getTheme() 83 | .obtainStyledAttributes(attrs, R.styleable.WeChatQrScannerView, 0, 0); 84 | 85 | int sideBg = a.getResourceId(R.styleable.WeChatQrScannerView_side_bg, 0); 86 | int movingBar = a.getResourceId(R.styleable.WeChatQrScannerView_moving_bar, 0); 87 | int barWidth = (int) a.getDimension(R.styleable.WeChatQrScannerView_bar_width, 0); 88 | int barHeight = (int) a.getDimension(R.styleable.WeChatQrScannerView_bar_height, 0); 89 | mMovingSpeed = a.getInteger(R.styleable.WeChatQrScannerView_moving_speed, 3); 90 | mBarMargin = (int) a.getDimension(R.styleable.WeChatQrScannerView_bar_margin, 10); 91 | mCurPos = mBarMargin; 92 | a.recycle(); 93 | 94 | mMovingBar = new ImageView(context); 95 | addView(mMovingBar); 96 | LayoutParams params = new LayoutParams( 97 | barWidth == 0 ? ViewGroup.LayoutParams.WRAP_CONTENT : barWidth, 98 | barHeight == 0 ? ViewGroup.LayoutParams.WRAP_CONTENT : barHeight); 99 | params.gravity = Gravity.CENTER_HORIZONTAL; 100 | params.topMargin = mCurPos; 101 | mMovingBar.setLayoutParams(params); 102 | mMovingBar.setImageResource(movingBar); 103 | setBackgroundResource(sideBg); 104 | } 105 | 106 | public void setMovingBar(@DrawableRes int movingBar) { 107 | mMovingBar.setImageResource(movingBar); 108 | } 109 | 110 | public void setSideBg(@DrawableRes int sideBg) { 111 | setBackgroundResource(sideBg); 112 | } 113 | 114 | public void setMovingSpeed(int speed) { 115 | mMovingSpeed = speed; 116 | } 117 | 118 | public void setBarMargin(int margin) { 119 | mBarMargin = margin; 120 | } 121 | 122 | public void start() { 123 | post(mMovingTask); 124 | } 125 | 126 | public void stop() { 127 | removeCallbacks(mMovingTask); 128 | } 129 | 130 | @Override 131 | protected void onAttachedToWindow() { 132 | super.onAttachedToWindow(); 133 | mHeight = getHeight(); 134 | start(); 135 | } 136 | 137 | @Override 138 | protected void onDetachedFromWindow() { 139 | super.onDetachedFromWindow(); 140 | stop(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /RxQrCode/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.jakewharton.butterknife' 3 | apply plugin: 'me.tatarka.retrolambda' 4 | 5 | android { 6 | compileSdkVersion rootProject.ext.androidCompileSdkVersion 7 | buildToolsVersion rootProject.ext.androidBuildToolsVersion 8 | 9 | defaultConfig { 10 | minSdkVersion rootProject.ext.minSdkVersion 11 | targetSdkVersion rootProject.ext.targetSdkVersion 12 | versionCode rootProject.ext.releaseVersionCode 13 | versionName rootProject.ext.releaseVersionName 14 | 15 | applicationId "com.github.piasy.rxqrcode.example" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | 29 | packagingOptions { 30 | exclude 'META-INF/rxjava.properties' 31 | } 32 | } 33 | 34 | dependencies { 35 | compile "com.android.support:appcompat-v7:$rootProject.ext.androidSupportSdkVersion" 36 | 37 | compile "com.jakewharton:butterknife:$rootProject.ext.butterKnifeVersion" 38 | annotationProcessor "com.jakewharton:butterknife-compiler:$rootProject.ext.butterKnifeVersion" 39 | compile 'com.afollestad.material-dialogs:core:0.9.0.2' 40 | compile("io.reactivex:rxandroid:$rootProject.ext.rxAndroidVersion") { 41 | exclude module: 'rxjava' 42 | } 43 | 44 | compile project(':RxQrCode') 45 | } 46 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/piasy/tools/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/piasy/rxqrcode/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.piasy.rxqrcode.example; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import butterknife.ButterKnife; 7 | import butterknife.OnClick; 8 | import com.github.piasy.cameracompat.CameraCompat; 9 | 10 | public class MainActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | 16 | CameraCompat.init(getApplicationContext()); 17 | 18 | setContentView(R.layout.activity_main); 19 | ButterKnife.bind(this); 20 | } 21 | 22 | @OnClick(R2.id.mBtnQrScan) 23 | public void qrScan() { 24 | startActivity(new Intent(this, QrScanActivity.class)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/piasy/rxqrcode/example/QrScanActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.rxqrcode.example; 26 | 27 | import android.os.Bundle; 28 | import android.support.v7.app.AppCompatActivity; 29 | import android.view.WindowManager; 30 | import com.github.piasy.cameracompat.CameraCompat; 31 | 32 | public class QrScanActivity extends AppCompatActivity { 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); 38 | 39 | getSupportFragmentManager().beginTransaction() 40 | .add(android.R.id.content, new QrScanFragment()) 41 | .commit(); 42 | } 43 | 44 | @Override 45 | protected void onDestroy() { 46 | super.onDestroy(); 47 | CameraCompat.reset(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/piasy/rxqrcode/example/QrScanFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.rxqrcode.example; 26 | 27 | import android.app.Activity; 28 | import android.content.Intent; 29 | import android.net.Uri; 30 | import android.os.Bundle; 31 | import android.provider.MediaStore; 32 | import android.support.annotation.Nullable; 33 | import android.support.v4.app.Fragment; 34 | import android.text.TextUtils; 35 | import android.util.Log; 36 | import android.view.LayoutInflater; 37 | import android.view.View; 38 | import android.view.ViewGroup; 39 | import android.widget.ScrollView; 40 | import android.widget.TextView; 41 | import android.widget.Toast; 42 | import butterknife.BindView; 43 | import butterknife.ButterKnife; 44 | import butterknife.OnClick; 45 | import com.afollestad.materialdialogs.MaterialDialog; 46 | import com.github.piasy.cameracompat.CameraCompat; 47 | import com.github.piasy.rxqrcode.RxQrCode; 48 | import com.google.zxing.Result; 49 | import rx.Subscription; 50 | import rx.android.schedulers.AndroidSchedulers; 51 | import rx.schedulers.Schedulers; 52 | import rx.subjects.PublishSubject; 53 | import rx.subjects.Subject; 54 | 55 | /** 56 | * A simple {@link Fragment} subclass. 57 | */ 58 | public class QrScanFragment extends Fragment implements CameraCompat.ErrorHandler { 59 | private static final int PICK_IMAGE_RESULT = 100; 60 | 61 | @BindView(R2.id.mScrollView) 62 | ScrollView mScrollView; 63 | @BindView(R2.id.mTvResult) 64 | TextView mTvResult; 65 | 66 | private Subject mScanResult = PublishSubject.create().toSerialized(); 67 | private Subscription mScanResultSubscription; 68 | private Subscription mPreviewSubscription; 69 | 70 | public QrScanFragment() { 71 | // Required empty public constructor 72 | } 73 | 74 | @OnClick(R2.id.mScanFromPicture) 75 | public void scanFromPicture() { 76 | Intent galleryIntent = new Intent(Intent.ACTION_PICK, 77 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI); 78 | startActivityForResult(galleryIntent, PICK_IMAGE_RESULT); 79 | } 80 | 81 | @Override 82 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 83 | if (resultCode == Activity.RESULT_OK) { 84 | switch (requestCode) { 85 | case PICK_IMAGE_RESULT: 86 | if (data != null && data.getData() != null) { 87 | Uri imageFileUri = data.getData(); 88 | String realPath = UriUtil.getPath(getContext(), imageFileUri); 89 | if (!TextUtils.isEmpty(realPath)) { 90 | RxQrCode.scanFromPicture(realPath) 91 | .subscribeOn(Schedulers.io()) 92 | .observeOn(AndroidSchedulers.mainThread()) 93 | .subscribe(result -> { 94 | if (mTvResult != null) { 95 | mTvResult.setText(mTvResult.getText() 96 | + "\nresult " 97 | + result.getText()); 98 | mScrollView.fullScroll(View.FOCUS_DOWN); 99 | } 100 | }, e -> { 101 | Toast.makeText(getContext(), "code not found", 102 | Toast.LENGTH_SHORT).show(); 103 | }); 104 | } else { 105 | Toast.makeText(getContext(), "file not found", Toast.LENGTH_SHORT) 106 | .show(); 107 | } 108 | } else { 109 | Toast.makeText(getContext(), "no data", Toast.LENGTH_SHORT).show(); 110 | } 111 | break; 112 | default: 113 | break; 114 | } 115 | } 116 | 117 | super.onActivityResult(requestCode, resultCode, data); 118 | } 119 | 120 | @Override 121 | public void onCreate(@Nullable Bundle savedInstanceState) { 122 | super.onCreate(savedInstanceState); 123 | 124 | mPreviewSubscription = RxQrCode.scanFromCamera(savedInstanceState, 125 | getActivity().getSupportFragmentManager(), R.id.mPreviewContainer, this) 126 | .observeOn(AndroidSchedulers.mainThread()) 127 | .subscribe(mScanResult); 128 | } 129 | 130 | @Override 131 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 132 | Bundle savedInstanceState) { 133 | return inflater.inflate(R.layout.fragment_qr_scan, container, false); 134 | } 135 | 136 | @Override 137 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 138 | super.onViewCreated(view, savedInstanceState); 139 | ButterKnife.bind(this, view); 140 | 141 | observeScanResult(); 142 | } 143 | 144 | @Override 145 | public void onDestroy() { 146 | super.onDestroy(); 147 | if (mPreviewSubscription != null && !mPreviewSubscription.isUnsubscribed()) { 148 | mPreviewSubscription.unsubscribe(); 149 | mPreviewSubscription = null; 150 | } 151 | } 152 | 153 | private void observeScanResult() { 154 | mScanResultSubscription = mScanResult.subscribe(result -> { 155 | stopObserveScanResult(); 156 | new MaterialDialog.Builder(getContext()) 157 | .title("found qr code") 158 | .content(result.getText()) 159 | .positiveText("ok") 160 | .dismissListener(dialog -> { 161 | Log.d("QrScan", "request 1"); 162 | observeScanResult(); 163 | }) 164 | .show(); 165 | }); 166 | } 167 | 168 | private void stopObserveScanResult() { 169 | if (mScanResultSubscription != null && !mScanResultSubscription.isUnsubscribed()) { 170 | mScanResultSubscription.unsubscribe(); 171 | mScanResultSubscription = null; 172 | } 173 | } 174 | 175 | @Override 176 | public void onError(@CameraCompat.ErrorCode int code) { 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/piasy/rxqrcode/example/UriUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Piasy 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.github.piasy.rxqrcode.example; 26 | 27 | import android.content.ContentUris; 28 | import android.content.Context; 29 | import android.database.Cursor; 30 | import android.net.Uri; 31 | import android.os.Build; 32 | import android.os.Environment; 33 | import android.provider.DocumentsContract; 34 | import android.provider.MediaStore; 35 | 36 | /** 37 | * @author paulburke 38 | */ 39 | 40 | public final class UriUtil { 41 | private UriUtil() { 42 | // no instance 43 | } 44 | 45 | public static String getPath(final Context context, final Uri uri) { 46 | // DocumentProvider 47 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT 48 | && DocumentsContract.isDocumentUri(context, uri)) { 49 | // ExternalStorageProvider 50 | if (isExternalStorageDocument(uri)) { 51 | final String docId = DocumentsContract.getDocumentId(uri); 52 | final String[] split = docId.split(":"); 53 | final String type = split[0]; 54 | 55 | if ("primary".equalsIgnoreCase(type)) { 56 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 57 | } 58 | } else if (isDownloadsDocument(uri)) { 59 | 60 | final String id = DocumentsContract.getDocumentId(uri); 61 | final Uri contentUri = ContentUris.withAppendedId( 62 | Uri.parse("content://downloads/public_downloads"), Long.parseLong(id)); 63 | 64 | return getDataColumn(context, contentUri, null, null); 65 | } else if (isMediaDocument(uri)) { 66 | final String docId = DocumentsContract.getDocumentId(uri); 67 | final String[] split = docId.split(":"); 68 | final String type = split[0]; 69 | 70 | Uri contentUri = null; 71 | if ("image".equals(type)) { 72 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 73 | } else if ("video".equals(type)) { 74 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 75 | } else if ("audio".equals(type)) { 76 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 77 | } 78 | 79 | final String selection = "_id=?"; 80 | final String[] selectionArgs = new String[] { 81 | split[1] 82 | }; 83 | 84 | return getDataColumn(context, contentUri, selection, selectionArgs); 85 | } 86 | } else if ("content".equalsIgnoreCase(uri.getScheme())) { 87 | if (isGooglePhotosUri(uri)) { 88 | return uri.getLastPathSegment(); 89 | } 90 | 91 | return getDataColumn(context, uri, null, null); 92 | } else if ("file".equalsIgnoreCase(uri.getScheme())) { 93 | return uri.getPath(); 94 | } 95 | 96 | return null; 97 | } 98 | 99 | private static boolean isGooglePhotosUri(Uri uri) { 100 | return "com.google.android.apps.photos.content".equals(uri.getAuthority()); 101 | } 102 | 103 | private static boolean isMediaDocument(Uri uri) { 104 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 105 | } 106 | 107 | private static boolean isDownloadsDocument(Uri uri) { 108 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 109 | } 110 | 111 | private static boolean isExternalStorageDocument(Uri uri) { 112 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 113 | } 114 | 115 | private static String getDataColumn(Context context, Uri uri, String selection, 116 | String[] selectionArgs) { 117 | 118 | Cursor cursor = null; 119 | final String column = "_data"; 120 | final String[] projection = { 121 | column 122 | }; 123 | 124 | try { 125 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, 126 | null); 127 | if (cursor != null && cursor.moveToFirst()) { 128 | final int column_index = cursor.getColumnIndexOrThrow(column); 129 | return cursor.getString(column_index); 130 | } 131 | } finally { 132 | if (cursor != null) { 133 | cursor.close(); 134 | } 135 | } 136 | return null; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/moving_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Piasy/RxQrCode/bffb94719fa2258b636d9ebdc36c687e27123621/app/src/main/res/drawable-xhdpi/moving_bar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/side_bg.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Piasy/RxQrCode/bffb94719fa2258b636d9ebdc36c687e27123621/app/src/main/res/drawable-xhdpi/side_bg.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_scanner_pick_photo_bg_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_10black_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_5black_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 31 |