From fc4e5739de11979226f4b80a0f79acbf9953c5c9 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 20 May 2024 18:09:00 -0400 Subject: [PATCH] feat: Scan QR code from gallery Close #6. --- .../openeuicc/ui/ProfileDownloadFragment.kt | 39 +++++++++++++++++-- .../java/im/angry/openeuicc/util/Utils.kt | 16 ++++++++ .../main/res/drawable/ic_gallery_black.xml | 5 +++ .../res/menu/fragment_profile_download.xml | 6 +++ app-common/src/main/res/values/strings.xml | 1 + 5 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 app-common/src/main/res/drawable/ic_gallery_black.xml diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt index feb0ab0..98237dc 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt @@ -3,6 +3,7 @@ package im.angry.openeuicc.ui import android.annotation.SuppressLint import android.app.Dialog import android.content.DialogInterface +import android.graphics.BitmapFactory import android.os.Bundle import android.text.Editable import android.util.Log @@ -10,6 +11,7 @@ import android.view.* import android.widget.ProgressBar import android.widget.TextView import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.Toolbar import androidx.lifecycle.lifecycleScope import com.google.android.material.textfield.TextInputLayout @@ -54,13 +56,38 @@ class ProfileDownloadFragment : BaseMaterialDialogFragment(), private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result -> result.contents?.let { content -> Log.d(TAG, content) - val components = content.split("$") - if (components.size < 3 || components[0] != "LPA:1") return@registerForActivityResult - profileDownloadServer.editText?.setText(components[1]) - profileDownloadCode.editText?.setText(components[2]) + onScanResult(content) } } + private val gallerySelectorLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { result -> + if (result == null) return@registerForActivityResult + + lifecycleScope.launch(Dispatchers.IO) { + runCatching { + requireContext().contentResolver.openInputStream(result)?.let { input -> + val bmp = BitmapFactory.decodeStream(input) + input.close() + + decodeQrFromBitmap(bmp)?.let { + withContext(Dispatchers.Main) { + onScanResult(it) + } + } + + bmp.recycle() + } + } + } + } + + private fun onScanResult(result: String) { + val components = result.split("$") + if (components.size < 3 || components[0] != "LPA:1") return + profileDownloadServer.editText?.setText(components[1]) + profileDownloadCode.editText?.setText(components[2]) + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -103,6 +130,10 @@ class ProfileDownloadFragment : BaseMaterialDialogFragment(), }) true } + R.id.scan_from_gallery -> { + gallerySelectorLauncher.launch("image/*") + true + } R.id.ok -> { startDownloadProfile() true diff --git a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt index 6a43d00..a93e7d2 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt @@ -2,9 +2,14 @@ package im.angry.openeuicc.util import android.content.Context import android.content.pm.PackageManager +import android.graphics.Bitmap import android.se.omapi.SEService import android.telephony.TelephonyManager import androidx.fragment.app.Fragment +import com.google.zxing.BinaryBitmap +import com.google.zxing.RGBLuminanceSource +import com.google.zxing.common.HybridBinarizer +import com.google.zxing.qrcode.QRCodeReader import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.di.AppContainer @@ -88,3 +93,14 @@ suspend fun connectSEService(context: Context): SEService = suspendCoroutine { c } } } + +fun decodeQrFromBitmap(bmp: Bitmap): String? = + runCatching { + val pixels = IntArray(bmp.width * bmp.height) + bmp.getPixels(pixels, 0, bmp.width, 0, 0, bmp.width, bmp.height) + + val luminanceSource = RGBLuminanceSource(bmp.width, bmp.height, pixels) + val binaryBmp = BinaryBitmap(HybridBinarizer(luminanceSource)) + + QRCodeReader().decode(binaryBmp).text + }.getOrNull() diff --git a/app-common/src/main/res/drawable/ic_gallery_black.xml b/app-common/src/main/res/drawable/ic_gallery_black.xml new file mode 100644 index 0000000..048f74a --- /dev/null +++ b/app-common/src/main/res/drawable/ic_gallery_black.xml @@ -0,0 +1,5 @@ + + + diff --git a/app-common/src/main/res/menu/fragment_profile_download.xml b/app-common/src/main/res/menu/fragment_profile_download.xml index d89c52c..b75822a 100644 --- a/app-common/src/main/res/menu/fragment_profile_download.xml +++ b/app-common/src/main/res/menu/fragment_profile_download.xml @@ -7,6 +7,12 @@ android:title="@string/profile_download_scan" app:showAsAction="ifRoom"/> + + IMEI (Optional) Space remaining: %s Scan QR Code + Scan QR Code from Gallery Download Failed to download eSIM. Check your activation / QR code.