diff options
Diffstat (limited to 'ui/src/main/java/com/wireguard/android/util/QrCodeFromFileScanner.kt')
-rw-r--r-- | ui/src/main/java/com/wireguard/android/util/QrCodeFromFileScanner.kt | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/ui/src/main/java/com/wireguard/android/util/QrCodeFromFileScanner.kt b/ui/src/main/java/com/wireguard/android/util/QrCodeFromFileScanner.kt new file mode 100644 index 00000000..4ea2dc7a --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/util/QrCodeFromFileScanner.kt @@ -0,0 +1,84 @@ +/* + * Copyright © 2017-2025 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.android.util + +import android.content.ContentResolver +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.util.Log +import com.google.zxing.BinaryBitmap +import com.google.zxing.DecodeHintType +import com.google.zxing.NotFoundException +import com.google.zxing.RGBLuminanceSource +import com.google.zxing.Reader +import com.google.zxing.Result +import com.google.zxing.common.HybridBinarizer +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** + * Encapsulates the logic of scanning a barcode from a file, + * @property contentResolver - Resolver to read the incoming data + * @property reader - An instance of zxing's [Reader] class to parse the image + */ +class QrCodeFromFileScanner( + private val contentResolver: ContentResolver, + private val reader: Reader, +) { + private fun scanBitmapForResult(source: Bitmap): Result { + val width = source.width + val height = source.height + val pixels = IntArray(width * height) + source.getPixels(pixels, 0, width, 0, 0, width, height) + + val bBitmap = BinaryBitmap(HybridBinarizer(RGBLuminanceSource(width, height, pixels))) + return reader.decode(bBitmap, mapOf(DecodeHintType.TRY_HARDER to true)) + } + + private fun doScan(data: Uri): Result { + Log.d(TAG, "Starting to scan an image: $data") + contentResolver.openInputStream(data).use { inputStream -> + var bitmap: Bitmap? = null + var firstException: Throwable? = null + for (i in arrayOf(1, 2, 4, 8, 16, 32, 64, 128)) { + try { + val options = BitmapFactory.Options() + options.inSampleSize = i + bitmap = BitmapFactory.decodeStream(inputStream, null, options) + ?: throw IllegalArgumentException("Can't decode stream for bitmap") + return scanBitmapForResult(bitmap) + } catch (e: Throwable) { + bitmap?.recycle() + System.gc() + Log.e(TAG, "Original image scan at scale factor $i finished with error: $e") + if (firstException == null) + firstException = e + } + } + throw Exception(firstException) + } + } + + /** + * Attempts to parse incoming data + * @return result of the decoding operation + * @throws NotFoundException when parser didn't find QR code in the image + */ + suspend fun scan(data: Uri) = withContext(Dispatchers.Default) { doScan(data) } + + companion object { + private const val TAG = "QrCodeFromFileScanner" + + /** + * Given a reference to a file, check if this file could be parsed by this class + * @return true if the file can be parsed, false if not + */ + fun validContentType(contentResolver: ContentResolver, data: Uri): Boolean { + return contentResolver.getType(data)?.startsWith("image/") == true + } + } +} |