Use bundles for decoded scan handoff
Replace Scan parcelables in the decode navigation flow with plain Bundle data. This avoids BadParcelableException when Android lazily unparcels a Scan written with a different layout or legacy ZXingCpp format names. A bundle of primitives is stable across app updates and does not depend on custom parcel compatibility.
This commit is contained in:
@@ -16,6 +16,8 @@ import de.markusfisch.android.binaryeye.app.permissionGrantedCallback
|
||||
import de.markusfisch.android.binaryeye.app.prefs
|
||||
import de.markusfisch.android.binaryeye.app.setFragment
|
||||
import de.markusfisch.android.binaryeye.database.Scan
|
||||
import de.markusfisch.android.binaryeye.database.toBundle
|
||||
import de.markusfisch.android.binaryeye.database.toScan
|
||||
import de.markusfisch.android.binaryeye.fragment.DecodeFragment
|
||||
import de.markusfisch.android.binaryeye.fragment.EncodeFragment
|
||||
import de.markusfisch.android.binaryeye.fragment.HistoryFragment
|
||||
@@ -81,6 +83,7 @@ class MainActivity : AppCompatActivity() {
|
||||
private const val PREFERENCES = "preferences"
|
||||
private const val HISTORY = "history"
|
||||
private const val ENCODE = "encode"
|
||||
const val DECODED_SCAN = "decoded_scan"
|
||||
const val DECODED = "decoded"
|
||||
|
||||
private fun Intent.getFragmentForIntent(): Fragment? = when {
|
||||
@@ -101,7 +104,11 @@ class MainActivity : AppCompatActivity() {
|
||||
getStringExtra(ENCODE)
|
||||
)
|
||||
|
||||
hasExtra(DECODED) -> getScanExtra(DECODED)?.let {
|
||||
hasExtra(DECODED_SCAN) -> getScanBundleExtra(DECODED_SCAN)?.let {
|
||||
DecodeFragment.newInstance(it)
|
||||
}
|
||||
|
||||
hasExtra(DECODED) -> getLegacyScanExtra(DECODED)?.let {
|
||||
DecodeFragment.newInstance(it)
|
||||
}
|
||||
|
||||
@@ -139,14 +146,18 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
fun getDecodeIntent(context: Context, scan: Scan): Intent {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.putExtra(DECODED, scan)
|
||||
intent.putExtra(DECODED_SCAN, scan.toBundle())
|
||||
return intent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun Intent.getScanExtra(name: String): Scan? = if (
|
||||
private fun Intent.getScanBundleExtra(name: String) = getBundleExtra(name)
|
||||
?.toScan()
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun Intent.getLegacyScanExtra(name: String): Scan? = if (
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
) {
|
||||
try {
|
||||
@@ -163,4 +174,8 @@ private fun Intent.getScanExtra(name: String): Scan? = if (
|
||||
}
|
||||
} else {
|
||||
null
|
||||
} ?: getParcelableExtra(name)
|
||||
} ?: try {
|
||||
getParcelableExtra(name)
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.markusfisch.android.binaryeye.database
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.text.format.DateFormat
|
||||
@@ -7,6 +8,7 @@ import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.BitMatrix
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.ContentType
|
||||
import de.markusfisch.android.zxingcpp.ZxingCpp.Result
|
||||
import de.markusfisch.android.binaryeye.zxingcpp.migrateBarcodeFormatName
|
||||
import java.util.Locale
|
||||
|
||||
data class Scan(
|
||||
@@ -80,26 +82,6 @@ data class Scan(
|
||||
return result
|
||||
}
|
||||
|
||||
private constructor(parcel: Parcel) : this(
|
||||
text = parcel.readString() ?: "",
|
||||
raw = parcel.readSizedByteArray(),
|
||||
format = BarcodeFormat.valueOf(parcel.readString() ?: ""),
|
||||
errorCorrectionLevel = parcel.readString(),
|
||||
version = parcel.readString(),
|
||||
dataMask = parcel.readInt(),
|
||||
symbol = parcel.readBitMatrix(),
|
||||
sequenceSize = parcel.readInt(),
|
||||
sequenceIndex = parcel.readInt(),
|
||||
sequenceId = parcel.readString() ?: "",
|
||||
country = parcel.readString(),
|
||||
addOn = parcel.readString(),
|
||||
price = parcel.readString(),
|
||||
issueNumber = parcel.readString(),
|
||||
dateTime = parcel.readString() ?: "",
|
||||
id = parcel.readLong(),
|
||||
label = parcel.readString()
|
||||
)
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.apply {
|
||||
writeString(text)
|
||||
@@ -127,7 +109,9 @@ data class Scan(
|
||||
companion object {
|
||||
@JvmField
|
||||
val CREATOR = object : Parcelable.Creator<Scan> {
|
||||
override fun createFromParcel(parcel: Parcel) = Scan(parcel)
|
||||
override fun createFromParcel(parcel: Parcel) = readScanFromParcel(
|
||||
parcel
|
||||
)
|
||||
override fun newArray(size: Int) = arrayOfNulls<Scan>(size)
|
||||
}
|
||||
}
|
||||
@@ -158,6 +142,58 @@ fun Result.toScan(): Scan {
|
||||
)
|
||||
}
|
||||
|
||||
fun Scan.toBundle() = Bundle().apply {
|
||||
putString(SCAN_TEXT, text)
|
||||
putByteArray(SCAN_RAW, raw)
|
||||
putString(SCAN_FORMAT, format.name)
|
||||
putString(SCAN_ERROR_CORRECTION_LEVEL, errorCorrectionLevel)
|
||||
putString(SCAN_VERSION, version)
|
||||
putInt(SCAN_DATA_MASK, dataMask)
|
||||
putInt(SCAN_SYMBOL_WIDTH, symbol?.width ?: 0)
|
||||
putInt(SCAN_SYMBOL_HEIGHT, symbol?.height ?: 0)
|
||||
putByteArray(SCAN_SYMBOL_DATA, symbol?.data)
|
||||
putInt(SCAN_SEQUENCE_SIZE, sequenceSize)
|
||||
putInt(SCAN_SEQUENCE_INDEX, sequenceIndex)
|
||||
putString(SCAN_SEQUENCE_ID, sequenceId)
|
||||
putString(SCAN_COUNTRY, country)
|
||||
putString(SCAN_ADD_ON, addOn)
|
||||
putString(SCAN_PRICE, price)
|
||||
putString(SCAN_ISSUE_NUMBER, issueNumber)
|
||||
putString(SCAN_DATE_TIME, dateTime)
|
||||
putLong(SCAN_ID, id)
|
||||
putString(SCAN_LABEL, label)
|
||||
}
|
||||
|
||||
fun Bundle.toScan(): Scan? {
|
||||
val formatName = getString(SCAN_FORMAT)?.migrateBarcodeFormatName()
|
||||
if (formatName.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
return try {
|
||||
Scan(
|
||||
text = getString(SCAN_TEXT) ?: "",
|
||||
raw = getByteArray(SCAN_RAW),
|
||||
format = BarcodeFormat.valueOf(formatName),
|
||||
errorCorrectionLevel = getString(SCAN_ERROR_CORRECTION_LEVEL),
|
||||
version = getString(SCAN_VERSION),
|
||||
dataMask = getInt(SCAN_DATA_MASK, -1),
|
||||
symbol = getBitMatrix(),
|
||||
sequenceSize = getInt(SCAN_SEQUENCE_SIZE, -1),
|
||||
sequenceIndex = getInt(SCAN_SEQUENCE_INDEX, -1),
|
||||
sequenceId = getString(SCAN_SEQUENCE_ID) ?: "",
|
||||
country = getString(SCAN_COUNTRY),
|
||||
addOn = getString(SCAN_ADD_ON),
|
||||
price = getString(SCAN_PRICE),
|
||||
issueNumber = getString(SCAN_ISSUE_NUMBER),
|
||||
dateTime = getString(SCAN_DATE_TIME) ?: getDateTime(),
|
||||
id = getLong(SCAN_ID, 0L),
|
||||
label = getString(SCAN_LABEL)
|
||||
)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDateTime(
|
||||
time: Long = System.currentTimeMillis()
|
||||
) = DateFormat.format(
|
||||
@@ -169,6 +205,139 @@ private fun getDateTime(
|
||||
time % 1000
|
||||
)
|
||||
|
||||
private fun readScanFromParcel(parcel: Parcel): Scan {
|
||||
val text = parcel.readString() ?: ""
|
||||
val raw = parcel.readSizedByteArray()
|
||||
val format = parcel.readBarcodeFormat()
|
||||
val errorCorrectionLevel = parcel.readString()
|
||||
val version = parcel.readString()
|
||||
val dataMask = parcel.readInt()
|
||||
val tail = parcel.readScanParcelTail()
|
||||
return Scan(
|
||||
text = text,
|
||||
raw = raw,
|
||||
format = format,
|
||||
errorCorrectionLevel = errorCorrectionLevel,
|
||||
version = version,
|
||||
dataMask = dataMask,
|
||||
symbol = tail.symbol,
|
||||
sequenceSize = tail.sequenceSize,
|
||||
sequenceIndex = tail.sequenceIndex,
|
||||
sequenceId = tail.sequenceId,
|
||||
country = tail.country,
|
||||
addOn = tail.addOn,
|
||||
price = tail.price,
|
||||
issueNumber = tail.issueNumber,
|
||||
dateTime = tail.dateTime,
|
||||
id = tail.id,
|
||||
label = tail.label
|
||||
)
|
||||
}
|
||||
|
||||
private data class ScanParcelTail(
|
||||
val symbol: BitMatrix?,
|
||||
val sequenceSize: Int,
|
||||
val sequenceIndex: Int,
|
||||
val sequenceId: String,
|
||||
val country: String?,
|
||||
val addOn: String?,
|
||||
val price: String?,
|
||||
val issueNumber: String?,
|
||||
val dateTime: String,
|
||||
val id: Long,
|
||||
val label: String?
|
||||
)
|
||||
|
||||
private fun Parcel.readBarcodeFormat(): BarcodeFormat {
|
||||
val formatName = (readString() ?: "").migrateBarcodeFormatName()
|
||||
return BarcodeFormat.valueOf(formatName)
|
||||
}
|
||||
|
||||
private fun Parcel.readScanParcelTail(): ScanParcelTail {
|
||||
val position = dataPosition()
|
||||
val tail = readCurrentScanParcelTail()
|
||||
if (tail.isPlausible()) {
|
||||
return tail
|
||||
}
|
||||
setDataPosition(position)
|
||||
return readLegacyScanParcelTail()
|
||||
}
|
||||
|
||||
private fun Parcel.readCurrentScanParcelTail() = ScanParcelTail(
|
||||
symbol = readBitMatrix(),
|
||||
sequenceSize = readInt(),
|
||||
sequenceIndex = readInt(),
|
||||
sequenceId = readString() ?: "",
|
||||
country = readString(),
|
||||
addOn = readString(),
|
||||
price = readString(),
|
||||
issueNumber = readString(),
|
||||
dateTime = readString() ?: "",
|
||||
id = readLong(),
|
||||
label = readString()
|
||||
)
|
||||
|
||||
private fun Parcel.readLegacyScanParcelTail() = ScanParcelTail(
|
||||
symbol = null,
|
||||
sequenceSize = readInt(),
|
||||
sequenceIndex = readInt(),
|
||||
sequenceId = readString() ?: "",
|
||||
country = readString(),
|
||||
addOn = readString(),
|
||||
price = readString(),
|
||||
issueNumber = readString(),
|
||||
dateTime = readString() ?: "",
|
||||
id = readLong(),
|
||||
label = readString()
|
||||
)
|
||||
|
||||
private fun ScanParcelTail.isPlausible(): Boolean {
|
||||
if (!dateTime.matches(DATE_TIME_PATTERN)) {
|
||||
return false
|
||||
}
|
||||
if (id < 0L) {
|
||||
return false
|
||||
}
|
||||
if (sequenceSize < -1 || sequenceIndex < -1) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private val DATE_TIME_PATTERN = Regex(
|
||||
"""^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}:\d{3}$"""
|
||||
)
|
||||
|
||||
private fun Bundle.getBitMatrix(): BitMatrix? {
|
||||
val width = getInt(SCAN_SYMBOL_WIDTH, 0)
|
||||
val height = getInt(SCAN_SYMBOL_HEIGHT, 0)
|
||||
val data = getByteArray(SCAN_SYMBOL_DATA)
|
||||
if (width < 1 || height < 1 || data == null) {
|
||||
return null
|
||||
}
|
||||
return BitMatrix(width, height, data)
|
||||
}
|
||||
|
||||
private const val SCAN_TEXT = "text"
|
||||
private const val SCAN_RAW = "raw"
|
||||
private const val SCAN_FORMAT = "format"
|
||||
private const val SCAN_ERROR_CORRECTION_LEVEL = "error_correction_level"
|
||||
private const val SCAN_VERSION = "version"
|
||||
private const val SCAN_DATA_MASK = "data_mask"
|
||||
private const val SCAN_SYMBOL_WIDTH = "symbol_width"
|
||||
private const val SCAN_SYMBOL_HEIGHT = "symbol_height"
|
||||
private const val SCAN_SYMBOL_DATA = "symbol_data"
|
||||
private const val SCAN_SEQUENCE_SIZE = "sequence_size"
|
||||
private const val SCAN_SEQUENCE_INDEX = "sequence_index"
|
||||
private const val SCAN_SEQUENCE_ID = "sequence_id"
|
||||
private const val SCAN_COUNTRY = "country"
|
||||
private const val SCAN_ADD_ON = "add_on"
|
||||
private const val SCAN_PRICE = "price"
|
||||
private const val SCAN_ISSUE_NUMBER = "issue_number"
|
||||
private const val SCAN_DATE_TIME = "date_time"
|
||||
private const val SCAN_ID = "id"
|
||||
private const val SCAN_LABEL = "label"
|
||||
|
||||
private fun Parcel.writeSizedByteArray(array: ByteArray?) {
|
||||
val size = array?.size ?: 0
|
||||
writeInt(size)
|
||||
|
||||
@@ -54,6 +54,8 @@ import de.markusfisch.android.binaryeye.content.toBarcode
|
||||
import de.markusfisch.android.binaryeye.content.toErrorCorrectionInt
|
||||
import de.markusfisch.android.binaryeye.content.wipeLastShareFile
|
||||
import de.markusfisch.android.binaryeye.database.Scan
|
||||
import de.markusfisch.android.binaryeye.database.toBundle
|
||||
import de.markusfisch.android.binaryeye.database.toScan
|
||||
import de.markusfisch.android.binaryeye.io.askForFileName
|
||||
import de.markusfisch.android.binaryeye.io.toSaveResult
|
||||
import de.markusfisch.android.binaryeye.io.writeExternalFile
|
||||
@@ -135,15 +137,14 @@ class DecodeFragment : Fragment() {
|
||||
|
||||
val justScanned = activity?.intent?.hasExtra(
|
||||
MainActivity.DECODED
|
||||
) == true || activity?.intent?.hasExtra(
|
||||
MainActivity.DECODED_SCAN
|
||||
) == true
|
||||
closeAutomatically = prefs.closeAutomatically && justScanned
|
||||
|
||||
scan = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
arguments?.getParcelable(SCAN, Scan::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
arguments?.getParcelable(SCAN)
|
||||
} ?: throw IllegalArgumentException("DecodeFragment needs a Scan")
|
||||
scan = arguments?.getBundle(SCAN_BUNDLE)?.toScan()
|
||||
?: arguments?.getLegacyScan(SCAN)
|
||||
?: throw IllegalArgumentException("DecodeFragment needs a Scan")
|
||||
|
||||
isBinary = scan.text.isEmpty()
|
||||
originalBytes = scan.raw ?: scan.text.toByteArray()
|
||||
@@ -667,12 +668,13 @@ class DecodeFragment : Fragment() {
|
||||
|
||||
companion object {
|
||||
private const val SCAN = "scan"
|
||||
private const val SCAN_BUNDLE = "scan_bundle"
|
||||
private const val OPEN_DOCUMENT = 1
|
||||
private const val SCHEME_FILE = "file://"
|
||||
|
||||
fun newInstance(scan: Scan): Fragment {
|
||||
val args = Bundle()
|
||||
args.putParcelable(SCAN, scan)
|
||||
args.putBundle(SCAN_BUNDLE, scan.toBundle())
|
||||
val fragment = DecodeFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
@@ -680,6 +682,23 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun Bundle.getLegacyScan(name: String): Scan? = if (
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
) {
|
||||
try {
|
||||
getParcelable(name, Scan::class.java)
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
} ?: try {
|
||||
getParcelable(name)
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
|
||||
private inline fun <T : View> T.showIf(
|
||||
visible: Boolean,
|
||||
block: (T) -> Unit
|
||||
|
||||
Reference in New Issue
Block a user