diff --git a/app/build.gradle b/app/build.gradle index fa96ab9e369ce7b18e9acfd92e632f975b26a6cd..03db113c1a879ee92ea811dc5fd2fab1ef8d7bdd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -101,6 +101,15 @@ android { } } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = '11' + } + // needed to use android.test package (ActivityInstrumentationTestCase2 etc) when targetting sdk 28 (Android 9) - // see https://developer.android.com/training/testing/set-up-project useLibrary 'android.test.runner' @@ -133,7 +142,8 @@ dependencies { implementation 'androidx.exifinterface:exifinterface:1.3.7' implementation 'androidx.camera:camera-core:1.2.3' - implementation 'com.google.zxing:core:3.5.2' + implementation 'com.google.zxing:core:3.5.3' + implementation 'io.github.zxing-cpp:android:2.2.0' testImplementation 'junit:junit:4.13.2' diff --git a/app/src/main/java/net/sourceforge/opencamera/MainActivity.java b/app/src/main/java/net/sourceforge/opencamera/MainActivity.java index f58967d2fe898d02c7f397fc403c2623b8f68020..a1abbff88a73c157f978c4947f258c445ce79228 100644 --- a/app/src/main/java/net/sourceforge/opencamera/MainActivity.java +++ b/app/src/main/java/net/sourceforge/opencamera/MainActivity.java @@ -296,12 +296,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen //QRCode job = JobKt.Job(null); - coroutineScope = new CoroutineScope() { - @Override - public CoroutineContext getCoroutineContext() { - return Dispatchers.getMain().plus(job); - } - }; + coroutineScope = () -> Dispatchers.getMain().plus(job); qrImageAnalyzer = new QrImageAnalyzer(this, coroutineScope); setContentView(R.layout.activity_main); diff --git a/app/src/main/java/net/sourceforge/opencamera/ext/ImageProxy.kt b/app/src/main/java/net/sourceforge/opencamera/ext/ImageProxy.kt deleted file mode 100644 index 3b0d19456afd94def412d71280151712a7775823..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/sourceforge/opencamera/ext/ImageProxy.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 The LineageOS Project - * SPDX-License-Identifier: Apache-2.0 - */ - -package net.sourceforge.opencamera.ext - -import androidx.camera.core.ImageProxy -import com.google.zxing.PlanarYUVLuminanceSource - -private fun rotateYUVLuminancePlane(data: ByteArray, width: Int, height: Int): ByteArray { - val yuv = ByteArray(width * height) - // Rotate the Y luma - var i = 0 - for (x in 0 until width) { - for (y in height - 1 downTo 0) { - yuv[i] = data[y * width + x] - i++ - } - } - return yuv -} - -internal val ImageProxy.planarYUVLuminanceSource: PlanarYUVLuminanceSource - get() { - val plane = planes[0] - val buffer = plane.buffer - var bytes = ByteArray(buffer.remaining()) - buffer.get(bytes) - - var width = width - var height = height - - if (imageInfo.rotationDegrees == 90 || imageInfo.rotationDegrees == 270) { - bytes = rotateYUVLuminancePlane(bytes, width, height) - width = height.also { height = width } - } - - return PlanarYUVLuminanceSource( - bytes, width, height, 0, 0, width, height, true - ) - } diff --git a/app/src/main/java/net/sourceforge/opencamera/preview/OverlayQRCodeView.java b/app/src/main/java/net/sourceforge/opencamera/preview/OverlayQRCodeView.java index 90113f94c4833b92d2aa18afde2de5b9ccf86687..ad0f98d9327b46eba531b5ef5e7f375615b6635c 100644 --- a/app/src/main/java/net/sourceforge/opencamera/preview/OverlayQRCodeView.java +++ b/app/src/main/java/net/sourceforge/opencamera/preview/OverlayQRCodeView.java @@ -38,7 +38,14 @@ public class OverlayQRCodeView extends View { private void init() { qrcode = ContextCompat.getDrawable(this.getContext(), R.drawable.scan_area); - DisplayMetrics displayMetrics =this.getContext().getResources().getDisplayMetrics(); + + // Ensure the view recalculates the position based on the current orientation + addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, + oldTop, oldRight, oldBottom) -> updateQRCodeBounds()); + } + + private void updateQRCodeBounds() { + DisplayMetrics displayMetrics = this.getContext().getResources().getDisplayMetrics(); final int screenWidth = displayMetrics.widthPixels; final int screenHeight = displayMetrics.heightPixels; @@ -51,7 +58,10 @@ public class OverlayQRCodeView extends View { isValid = false; } else { qrcode.setBounds(left, top, right, bottom); + isValid = true; } + + invalidate(); // Redraw the view } @Override diff --git a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java index 2f9777857e678c9e874d58605a97d216a1606a21..224cd7f7e227f4bee081f461f5a7c11bd0ce23c6 100644 --- a/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java +++ b/app/src/main/java/net/sourceforge/opencamera/preview/Preview.java @@ -101,14 +101,6 @@ import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.BinaryBitmap; -import com.google.zxing.LuminanceSource; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.Reader; -import com.google.zxing.Result; -import com.google.zxing.common.HybridBinarizer; -import com.google.zxing.RGBLuminanceSource; /** This class was originally named due to encapsulating the camera preview, * but in practice it's grown to more than this, and includes most of the @@ -1134,32 +1126,11 @@ public class Preview implements SurfaceHolder.Callback, TextureView.SurfaceTextu enablePreviewBitmap(); } TextureView textureView = (TextureView) this.cameraSurface; - Bitmap bitmap = textureView.getBitmap(preview_bitmap); - - if (bitmap!=null) { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - int[] pixels = new int[width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - - LuminanceSource source = new RGBLuminanceSource(width, height, pixels); - BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); - Reader reader = new MultiFormatReader(); - - try { - Result result = reader.decode(binaryBitmap); - String qrcodeContent = result.getText(); - MainActivity mActivity = (MainActivity) this.getContext(); - if (MyDebug.LOG) - Log.d(TAG, "Find QRCode qrcodeContent="+qrcodeContent ); - if (result.getBarcodeFormat() == BarcodeFormat.QR_CODE) { - mActivity.qrImageAnalyzer.showQrDialog(result); - } - } catch (Exception e) { - // K1ZFP TODO Error 2 - } - } else { - // K1ZFP TODO Error 1 + Bitmap bitmap = textureView.getBitmap(preview_bitmap); + MainActivity mActivity = (MainActivity) this.getContext(); + int rotation = getDisplayRotationDegrees(false); + if (bitmap != null) { + mActivity.qrImageAnalyzer.readImage(bitmap, rotation); } } else if (!previewBitmapWasEnabled) { disablePreviewBitmap(); diff --git a/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt index e4676434eab2dfc4e873239a36d33ecebaf558e0..0b475dc310a048772ff59c058f892bde679648f3 100644 --- a/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt +++ b/app/src/main/java/net/sourceforge/opencamera/qr/QrImageAnalyzer.kt @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 The LineageOS Project + * SPDX-FileCopyrightText: 2022-2024 The LineageOS Project * SPDX-License-Identifier: Apache-2.0 */ @@ -13,8 +13,11 @@ import android.content.ClipDescription import android.content.ClipboardManager import android.content.Intent import android.content.pm.ActivityInfo +import android.graphics.Bitmap +import android.graphics.Rect import android.os.Build import android.text.method.LinkMovementMethod +import android.util.Log import android.view.textclassifier.TextClassificationManager import android.widget.ImageButton import android.widget.ImageView @@ -23,30 +26,32 @@ import android.widget.TextView import android.widget.Toast import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.LinearLayoutCompat.LayoutParams +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy import androidx.cardview.widget.CardView import androidx.core.graphics.drawable.DrawableCompat import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton -import com.google.zxing.MultiFormatReader +import com.google.zxing.BarcodeFormat import com.google.zxing.Result -import net.sourceforge.opencamera.R import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import net.sourceforge.opencamera.ext.getThemeColor +import net.sourceforge.opencamera.MyDebug +import net.sourceforge.opencamera.R import net.sourceforge.opencamera.ext.px +import zxingcpp.BarcodeReader import kotlin.reflect.cast -class QrImageAnalyzer(private val activity: Activity, private val scope: CoroutineScope) { - +class QrImageAnalyzer(private val activity: Activity, private val scope: CoroutineScope) : + ImageAnalysis.Analyzer { // Views private val bottomSheetDialog by lazy { BottomSheetDialog(activity).apply { setContentView(R.layout.qr_bottom_sheet_dialog) } } - private val bottomSheetDialogCardView by lazy { bottomSheetDialog.findViewById(R.id.cardView)!! } @@ -69,6 +74,8 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti bottomSheetDialog.findViewById(R.id.actionsLayout)!! } + private val tag = "QrImageAnalyzer" + // System services private val clipboardManager by lazy { activity.getSystemService(ClipboardManager::class.java) } private val keyguardManager by lazy { activity.getSystemService(KeyguardManager::class.java) } @@ -77,13 +84,53 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti } // QR - private val reader by lazy { MultiFormatReader() } + private val reader by lazy { + BarcodeReader().apply { + options.tryInvert = true + options.tryRotate = true + } + } private val qrTextClassifier by lazy { QrTextClassifier(activity, textClassificationManager.textClassifier) } - public fun showQrDialog(result: Result) { + override fun analyze(image: ImageProxy) { + image.use { + reader.read(image).firstOrNull()?.let { + showQrDialog(it) + } + } + } + + fun readImage(bitmap: Bitmap, rotation: Int) { + // We want to scan the full bitmap + val cropRect = Rect(0, 0, bitmap.width, bitmap.height) + + // If we want to scan a specific area, e.g., middle portion, we can adjust the coordinates like this: + // For example, scan the central 50% of the bitmap: + //val left = (width * 0.25).toInt() // 25% from the left + //val top = (height * 0.25).toInt() // 25% from the top + //val right = (width * 0.75).toInt() // 75% from the left (50% width in total) + //val bottom = (height * 0.75).toInt() // 75% from the top (50% height in total) + + // Define the customized Rect + //cropRect = Rect(left, top, right, bottom) + + try { + val results: List = reader.read(bitmap, cropRect, rotation) + val qrcodeContent = results.toString() + if (MyDebug.LOG) { + Log.d(tag, "Find QRCode qrcodeContent = $qrcodeContent") + } + + results.firstOrNull()?.let { + showQrDialog(it) + } + } catch (ignored: Exception) { } + } + + private fun showQrDialog(result: BarcodeReader.Result) { scope.launch(Dispatchers.Main) { if (bottomSheetDialog.isShowing) { return@launch @@ -94,7 +141,31 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti // Classify message val textClassification = withContext(Dispatchers.IO) { - qrTextClassifier.classifyText(result) + qrTextClassifier.classifyText( + Result( + text, result.bytes, null, when (result.format) { + BarcodeReader.Format.NONE -> null + BarcodeReader.Format.AZTEC -> BarcodeFormat.AZTEC + BarcodeReader.Format.CODABAR -> BarcodeFormat.CODABAR + BarcodeReader.Format.CODE_39 -> BarcodeFormat.CODE_39 + BarcodeReader.Format.CODE_93 -> BarcodeFormat.CODE_93 + BarcodeReader.Format.CODE_128 -> BarcodeFormat.CODE_128 + BarcodeReader.Format.DATA_BAR -> null + BarcodeReader.Format.DATA_BAR_EXPANDED -> null + BarcodeReader.Format.DATA_MATRIX -> BarcodeFormat.DATA_MATRIX + BarcodeReader.Format.EAN_8 -> BarcodeFormat.EAN_8 + BarcodeReader.Format.EAN_13 -> BarcodeFormat.EAN_13 + BarcodeReader.Format.ITF -> BarcodeFormat.ITF + BarcodeReader.Format.MAXICODE -> BarcodeFormat.MAXICODE + BarcodeReader.Format.PDF_417 -> BarcodeFormat.PDF_417 + BarcodeReader.Format.QR_CODE -> BarcodeFormat.QR_CODE + BarcodeReader.Format.MICRO_QR_CODE -> BarcodeFormat.QR_CODE + BarcodeReader.Format.RMQR_CODE -> BarcodeFormat.QR_CODE + BarcodeReader.Format.UPC_A -> BarcodeFormat.UPC_A + BarcodeReader.Format.UPC_E -> BarcodeFormat.UPC_E + } + ) + ) } bottomSheetDialogData.text = textClassification.text @@ -182,7 +253,7 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti action = Intent.ACTION_SEND type = ClipDescription.MIMETYPE_TEXT_PLAIN putExtra( - Intent.EXTRA_TEXT, result.text + Intent.EXTRA_TEXT, text ) }, activity.getString(R.string.abc_shareactionprovider_share_with) @@ -191,14 +262,11 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti } // Show dialog - activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - + activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT bottomSheetDialog.show() - bottomSheetDialog.setOnDismissListener { - activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED } - } } @@ -211,5 +279,4 @@ class QrImageAnalyzer(private val activity: Activity, private val scope: Corouti ).apply { layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) } - } diff --git a/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt b/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt index 7a4ffb63d8afafb6528e834091c0644380c9e140..3db7ed9a5866db78b11fd24934126933a8c5963f 100644 --- a/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt +++ b/app/src/main/java/net/sourceforge/opencamera/qr/QrTextClassifier.kt @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 The LineageOS Project + * SPDX-FileCopyrightText: 2023-2024 The LineageOS Project * SPDX-License-Identifier: Apache-2.0 */ @@ -84,6 +84,26 @@ class QrTextClassifier( } } .build() + + SCHEME_UPI -> return TextClassification.Builder() + .setText(context.getString(R.string.qr_upi_content_description)) + .setEntityType(TextClassifier.TYPE_OTHER, 1.0f) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + addAction( + RemoteAction::class.build( + context, + R.drawable.ic_currency_rupee, + R.string.qr_upi_title, + R.string.qr_upi_content_description, + Intent(Intent.ACTION_VIEW).apply { + data = uri + } + ) + ) + } + } + .build() } } @@ -97,5 +117,6 @@ class QrTextClassifier( companion object { private const val SCHEME_DPP = "dpp" private const val SCHEME_FIDO = "fido" + private const val SCHEME_UPI = "upi" } } diff --git a/app/src/main/res/drawable/ic_currency_rupee.xml b/app/src/main/res/drawable/ic_currency_rupee.xml new file mode 100644 index 0000000000000000000000000000000000000000..98437f96a70635b4c3f187aa2be42bcf8a2d19ef --- /dev/null +++ b/app/src/main/res/drawable/ic_currency_rupee.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 32101a90c7e374d418301068786630f7cf71329f..5f9a4747c420da62c4e8c8e2948f6427fb2617b7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1111,15 +1111,27 @@ Skip LENS + + + Copy to clipboard + Icon + Share + No app available to handle this action + + QRCode Add contact Add contact Add event to calendar Add this event to the calendar - Open this location - Open this location + Wi-Fi Easy Connectâ„¢ (DPP) + Configure this device Send a new email Compose a new email to the specified emails + Use passkey + Handle this FIDO QR code + Open this location + Open this location Lookup this ISBN Search this ISBN on isbnsearch.org Lookup product @@ -1128,19 +1140,16 @@ Send a new SMS to the specified recipients Call phone number Call the phone number + Text + Choose app to pay + Open this UPI link with the appropriate app if supported + Open URL + Open this URL with the appropriate app if supported Lookup VIN Lookup this Vehicle Identification Number (VIN) Connect to this Wi-Fi network Add this Wi-Fi network to the list of known networks and connect the device to it - Icon - Share - Copy to clipboard - No app available to handle this action - Text - Configure this device - Wi-Fi Easy Connectâ„¢ (DPP) - Handle this FIDO QR code - Use passkey + Share with "YOUR PRIVACY SWITCH MUST BE ACTIVATED" diff --git a/build.gradle b/build.gradle index d8593c698f2621f67a88de9e200b4636561b5a1f..001f3b2024caf5cc0e8ef240dae1c0d3052e8b62 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:8.2.1' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0' } }