feat: Scan QR code from gallery

Close #6.
This commit is contained in:
Peter Cai 2024-05-20 18:09:00 -04:00
parent 8eb36c77a8
commit fc4e5739de
5 changed files with 63 additions and 4 deletions

View file

@ -3,6 +3,7 @@ package im.angry.openeuicc.ui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.content.DialogInterface import android.content.DialogInterface
import android.graphics.BitmapFactory
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.util.Log import android.util.Log
@ -10,6 +11,7 @@ import android.view.*
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
@ -54,13 +56,38 @@ class ProfileDownloadFragment : BaseMaterialDialogFragment(),
private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result -> private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
result.contents?.let { content -> result.contents?.let { content ->
Log.d(TAG, content) Log.d(TAG, content)
val components = content.split("$") onScanResult(content)
if (components.size < 3 || components[0] != "LPA:1") return@registerForActivityResult
profileDownloadServer.editText?.setText(components[1])
profileDownloadCode.editText?.setText(components[2])
} }
} }
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( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -103,6 +130,10 @@ class ProfileDownloadFragment : BaseMaterialDialogFragment(),
}) })
true true
} }
R.id.scan_from_gallery -> {
gallerySelectorLauncher.launch("image/*")
true
}
R.id.ok -> { R.id.ok -> {
startDownloadProfile() startDownloadProfile()
true true

View file

@ -2,9 +2,14 @@ package im.angry.openeuicc.util
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.se.omapi.SEService import android.se.omapi.SEService
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import androidx.fragment.app.Fragment 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.OpenEuiccApplication
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.di.AppContainer 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()

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M22,16L22,4c0,-1.1 -0.9,-2 -2,-2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zM11,12l2.03,2.71L16,11l4,5L8,16l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6L2,6z"/>
</vector>

View file

@ -7,6 +7,12 @@
android:title="@string/profile_download_scan" android:title="@string/profile_download_scan"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
<item
android:id="@+id/scan_from_gallery"
android:icon="@drawable/ic_gallery_black"
android:title="@string/profile_download_scan_from_gallery"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/ok" android:id="@+id/ok"
android:icon="@drawable/ic_check_black" android:icon="@drawable/ic_check_black"

View file

@ -31,6 +31,7 @@
<string name="profile_download_imei">IMEI (Optional)</string> <string name="profile_download_imei">IMEI (Optional)</string>
<string name="profile_download_free_space">Space remaining: %s</string> <string name="profile_download_free_space">Space remaining: %s</string>
<string name="profile_download_scan">Scan QR Code</string> <string name="profile_download_scan">Scan QR Code</string>
<string name="profile_download_scan_from_gallery">Scan QR Code from Gallery</string>
<string name="profile_download_ok">Download</string> <string name="profile_download_ok">Download</string>
<string name="profile_download_failed">Failed to download eSIM. Check your activation / QR code.</string> <string name="profile_download_failed">Failed to download eSIM. Check your activation / QR code.</string>