Show parsed content types in history

Store content types and use it in the history.
This commit is contained in:
Markus Fisch
2026-06-05 18:20:07 +02:00
parent d5ffcabdd2
commit f90aaac4f9
7 changed files with 382 additions and 216 deletions
@@ -24,21 +24,12 @@ import android.widget.LinearLayout
import android.widget.TableLayout
import android.widget.TableRow
import android.widget.TextView
import androidx.core.net.toUri
import androidx.core.widget.TextViewCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.ActionRegistry
import de.markusfisch.android.binaryeye.actions.mail.MatMsg
import de.markusfisch.android.binaryeye.actions.mail.MatMsgAction
import de.markusfisch.android.binaryeye.actions.otpauth.OtpauthAction
import de.markusfisch.android.binaryeye.actions.vtype.VTypeParser
import de.markusfisch.android.binaryeye.actions.vtype.vcard.VCardAction
import de.markusfisch.android.binaryeye.actions.vtype.vevent.VEventAction
import de.markusfisch.android.binaryeye.actions.web.WebAction
import de.markusfisch.android.binaryeye.actions.wifi.WifiAction
import de.markusfisch.android.binaryeye.actions.wifi.WifiConnector
import de.markusfisch.android.binaryeye.adapter.prettifyFormatName
import de.markusfisch.android.binaryeye.adapter.toFormatDescriptionResId
import de.markusfisch.android.binaryeye.app.db
@@ -46,13 +37,10 @@ import de.markusfisch.android.binaryeye.app.hasLocationPermission
import de.markusfisch.android.binaryeye.app.hasWritePermission
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.content.ContentBarcode
import de.markusfisch.android.binaryeye.content.EpcQrParser
import de.markusfisch.android.binaryeye.content.IdlParser
import de.markusfisch.android.binaryeye.content.SealParser
import de.markusfisch.android.binaryeye.content.ParsedContentType
import de.markusfisch.android.binaryeye.content.copyToClipboard
import de.markusfisch.android.binaryeye.content.epcQrToRes
import de.markusfisch.android.binaryeye.content.idlToRes
import de.markusfisch.android.binaryeye.content.openUrl
import de.markusfisch.android.binaryeye.content.parseData
import de.markusfisch.android.binaryeye.content.shareAsFile
import de.markusfisch.android.binaryeye.content.shareText
import de.markusfisch.android.binaryeye.content.toBarcode
@@ -74,11 +62,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONException
import org.json.JSONObject
import java.io.File
import java.security.MessageDigest
import kotlin.math.roundToInt
import de.markusfisch.android.binaryeye.content.ParsedField as Field
class DecodeActivity : AbstractBaseActivity() {
private lateinit var labelHeadlineView: TextView
@@ -371,19 +358,20 @@ class DecodeActivity : AbstractBaseActivity() {
isEditing: Boolean
) {
val items = mutableListOf<Field>()
val parsedDataTitleResId = parseData(items, text, bytes)
val parsedData = parseData(this@DecodeActivity, text, bytes, format, action)
items.addAll(parsedData.fields)
when (prefs.showChecksum) {
"CRC4" -> items.addField(R.string.crc4, String.format("%X", crc4(bytes)))
"MD5" -> items.addField(R.string.md5, bytes.md5().toHexString().fold())
"SHA1" -> items.addField(R.string.sha1, bytes.sha1().toHexString().fold())
"SHA256" -> items.addField(R.string.sha256, bytes.sha256().toHexString().fold())
}
if (parsedDataTitleResId != 0) {
if (parsedData.type != ParsedContentType.UNKNOWN) {
items.add(
0,
Field(
R.string.parsed_type,
getString(parsedDataTitleResId)
getString(parsedData.type.resId)
)
)
lastParsedDataItems = items
@@ -399,135 +387,6 @@ class DecodeActivity : AbstractBaseActivity() {
fillDataItems(items, false)
}
private fun parseData(
items: MutableList<Field>,
text: String,
bytes: ByteArray
): Int {
val ctx = this
val trackingLink = bytes.generateDpTrackingLink(format)
if (trackingLink != null) {
items.addField(
getString(R.string.tracking_number),
trackingLink.label,
trackingLink.link,
)
return R.string.parsed_type_deutsche_post
}
try {
items.addField(
R.string.formatted_json,
JSONObject(text).toString(2)
)
return R.string.parsed_type_json
} catch (_: JSONException) {
// Ignore
}
IdlParser.parse(String(bytes))?.let {
items.addField("IIN", it.iin)
it.elements.forEach { (id, value) ->
items.addField(ctx.idlToRes(id), value)
}
return R.string.parsed_type_international_driver_license
}
SealParser.parse(ctx, bytes)?.let { sealFields ->
sealFields.forEach { vf ->
items.addField(vf.name, vf.value)
}
return R.string.parsed_type_digital_seal
}
EpcQrParser.parse(text)?.let {
items.addAll(it.map { (id, value) ->
Field(ctx.epcQrToRes(id), value)
})
return R.string.parsed_type_sepa_epc_qr
}
when (action) {
is MatMsgAction -> MatMsg(text).run {
items.addField(R.string.email_to, to)
items.addField(R.string.email_subject, sub)
items.addField(R.string.email_body, body)
return R.string.parsed_type_email
}
is VCardAction,
is VEventAction -> VTypeParser.parseMap(text).let { parsedMap ->
parsedMap.forEach { item ->
items.addField(item.key, item.value.joinToString("\n") { it.value })
}
return if (action is VCardAction) {
R.string.parsed_type_contact_card
} else {
R.string.parsed_type_calendar_event
}
}
is WebAction -> try {
text.toUri().run {
items.addField(R.string.scheme, scheme)
items.addField(R.string.host, host)
items.addField(R.string.query, query)
}
return R.string.parsed_type_url
} catch (_: Exception) {
// Ignore
}
is OtpauthAction -> try {
text.toUri().run {
val label = pathSegments.firstOrNull()?.let {
Uri.decode(it)
} ?: ""
val colonIdx = label.indexOf(':')
val issuerFromLabel = if (colonIdx >= 0) {
label.substring(0, colonIdx)
} else {
null
}
val account = if (colonIdx >= 0) {
label.substring(colonIdx + 1).trim()
} else {
label
}
val issuer = getQueryParameter("issuer") ?: issuerFromLabel
items.addField(R.string.entry_type, host?.uppercase())
items.addField(R.string.otp_account, account)
items.addField(R.string.otp_issuer, issuer)
items.addField(R.string.otp_algorithm, getQueryParameter("algorithm"))
items.addField(R.string.otp_digits, getQueryParameter("digits"))
if (host == "totp") {
items.addField(R.string.otp_period, getQueryParameter("period"))
} else {
items.addField(R.string.otp_counter, getQueryParameter("counter"))
}
}
return R.string.parsed_type_otp
} catch (_: Exception) {
// Ignore
}
is WifiAction -> WifiConnector.parseMap(text)?.let { wifiData ->
items.addField(R.string.entry_type, getString(R.string.wifi_network))
items.addField(R.string.wifi_ssid, wifiData["S"])
items.addField(R.string.wifi_password, wifiData["P"])
items.addField(R.string.wifi_type, wifiData["T"])
items.addField(R.string.wifi_hidden, wifiData["H"])
items.addField(R.string.wifi_eap, wifiData["E"])
items.addField(R.string.wifi_identity, wifiData["I"])
items.addField(R.string.wifi_anonymous_identity, wifiData["A"])
items.addField(R.string.wifi_phase2, wifiData["PH2"])
return R.string.parsed_type_wifi_network
}
}
return 0
}
private fun TableLayout.fillMetaView(scan: Scan) {
val items = mutableListOf(
Field(R.string.error_correction_level, scan.errorCorrectionLevel),
@@ -887,12 +746,6 @@ class DecodeActivity : AbstractBaseActivity() {
}
}
private data class Field(
val key: Any,
val value: CharSequence?,
val link: String? = null
)
private fun MutableList<Field>.addField(
key: Any,
value: CharSequence?,
@@ -959,57 +812,6 @@ private fun hexDump(bytes: ByteArray, charsPerLine: Int = 33): String {
private fun Int.positiveToString() = if (this > -1) this.toString() else ""
private data class TrackingLink(
val label: String,
val link: String
)
private fun ByteArray.generateDpTrackingLink(format: String): TrackingLink? {
// Check for Deutsche Post Matrixcode stamp.
var isStamp = false
var rawData = this
if (format == ZxingCpp.BarcodeFormat.DataMatrix.name &&
toString(Charsets.ISO_8859_1).startsWith("DEA5")
) {
if (size == 47) {
isStamp = true
} else if (size > 47) {
// Transform back to original data.
rawData = toString(Charsets.UTF_8).toByteArray(
Charsets.ISO_8859_1
)
if (rawData.size == 47) {
isStamp = true
}
}
}
if (!isStamp) {
return null
}
val hex = StringBuilder()
hex.append(String.format("%02X", rawData[9]))
hex.append(String.format("%02X", rawData[10]))
hex.append(String.format("%02X", rawData[11]))
hex.append(String.format("%02X", rawData[12]))
hex.append(String.format("%02X", rawData[13]))
hex.append(String.format("%X", (rawData[4].toInt() and 0x0f).toByte()))
hex.append(String.format("%02X", rawData[5]))
hex.append(String.format("%02X", rawData[6]))
hex.append(String.format("%02X", rawData[7]))
hex.append(String.format("%02X", rawData[8]))
val hexString = hex.toString()
val trackingNumber = hexString + String.format(
"%X",
crc4(hexString.toByteArray(Charsets.ISO_8859_1))
)
return TrackingLink(
trackingNumber,
"https://www.deutschepost.de/de/s/sendungsverfolgung.html?piececode=$trackingNumber",
)
}
// CRC-4 with polynomial x^4 + x + 1.
private fun crc4(input: ByteArray): Int {
var crc = 0
@@ -12,6 +12,8 @@ import android.widget.ImageView
import android.widget.Spinner
import android.widget.TextView
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.content.ParsedContentType
import de.markusfisch.android.binaryeye.content.parsedContentTypeFromName
import de.markusfisch.android.binaryeye.database.Database
import java.text.DateFormat
import java.text.ParseException
@@ -29,6 +31,7 @@ class ScansAdapter(
private val timeIndex = cursor.getColumnIndex(Database.SCANS_DATETIME)
private val nameIndex = cursor.getColumnIndex(Database.SCANS_NAME)
private val textIndex = cursor.getColumnIndex(Database.SCANS_TEXT)
private val contentTypeIndex = cursor.getColumnIndex(Database.SCANS_CONTENT_TYPE)
private val formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
private val selections = mutableMapOf<Long, Int>()
fun select(view: View, id: Long, position: Int) {
@@ -113,7 +116,7 @@ class ScansAdapter(
}
text.isNullOrEmpty() -> context.getString(
R.string.binary_data
cursor.getString(contentTypeIndex).toBinaryDataStringResId()
)
else -> text
@@ -201,6 +204,15 @@ fun Context.setupFormatSpinner(spinner: Spinner): List<String> {
fun String.prettifyFormatName() = replace("_", " ")
.replace(formatWordBoundary, " ")
private fun String?.toBinaryDataStringResId(): Int {
val type = parsedContentTypeFromName(this)
return if (type == ParsedContentType.UNKNOWN) {
R.string.binary_data
} else {
type.resId
}
}
fun String.toFormatDescriptionResId(): Int = when (this) {
"Aztec" -> R.string.format_description_aztec
"Codabar" -> R.string.format_description_codabar
@@ -0,0 +1,194 @@
package de.markusfisch.android.binaryeye.content
import android.content.Context
import android.net.Uri
import androidx.core.net.toUri
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.Action
import de.markusfisch.android.binaryeye.actions.ActionRegistry
import de.markusfisch.android.binaryeye.actions.mail.MatMsg
import de.markusfisch.android.binaryeye.actions.mail.MatMsgAction
import de.markusfisch.android.binaryeye.actions.otpauth.OtpauthAction
import de.markusfisch.android.binaryeye.actions.vtype.VTypeParser
import de.markusfisch.android.binaryeye.actions.vtype.vcard.VCardAction
import de.markusfisch.android.binaryeye.actions.vtype.vevent.VEventAction
import de.markusfisch.android.binaryeye.actions.web.WebAction
import de.markusfisch.android.binaryeye.actions.wifi.WifiAction
import de.markusfisch.android.binaryeye.actions.wifi.WifiConnector
import org.json.JSONException
import org.json.JSONObject
enum class ParsedContentType(
val resId: Int
) {
UNKNOWN(0),
DEUTSCHE_POST(R.string.parsed_type_deutsche_post),
JSON(R.string.parsed_type_json),
INTERNATIONAL_DRIVER_LICENSE(
R.string.parsed_type_international_driver_license
),
DIGITAL_SEAL(R.string.parsed_type_digital_seal),
SEPA_EPC_QR(R.string.parsed_type_sepa_epc_qr),
EMAIL(R.string.parsed_type_email),
URL(R.string.parsed_type_url),
OTP(R.string.parsed_type_otp),
CONTACT_CARD(R.string.parsed_type_contact_card),
CALENDAR_EVENT(R.string.parsed_type_calendar_event),
WIFI_NETWORK(R.string.parsed_type_wifi_network);
}
data class ParsedData(
val type: ParsedContentType,
val fields: List<ParsedField>
)
data class ParsedField(
val key: Any,
val value: CharSequence?,
val link: String? = null
)
fun parseData(
context: Context,
text: String,
bytes: ByteArray,
format: String,
action: Action = ActionRegistry.getAction(bytes)
): ParsedData {
val fields = mutableListOf<ParsedField>()
DeutschePostParser.parse(bytes, format)?.let {
fields.addField(
context.getString(R.string.tracking_number),
it.label,
it.link,
)
return ParsedData(ParsedContentType.DEUTSCHE_POST, fields)
}
try {
fields.addField(R.string.formatted_json, JSONObject(text).toString(2))
return ParsedData(ParsedContentType.JSON, fields)
} catch (_: JSONException) {
// Ignore
}
IdlParser.parse(String(bytes))?.let {
fields.addField("IIN", it.iin)
it.elements.forEach { (id, value) ->
fields.addField(context.idlToRes(id), value)
}
return ParsedData(ParsedContentType.INTERNATIONAL_DRIVER_LICENSE, fields)
}
SealParser.parse(context, bytes)?.let { sealFields ->
sealFields.forEach { vf ->
fields.addField(vf.name, vf.value)
}
return ParsedData(ParsedContentType.DIGITAL_SEAL, fields)
}
EpcQrParser.parse(text)?.let {
fields.addAll(it.map { (id, value) ->
ParsedField(context.epcQrToRes(id), value)
})
return ParsedData(ParsedContentType.SEPA_EPC_QR, fields)
}
when (action) {
is MatMsgAction -> MatMsg(text).run {
fields.addField(R.string.email_to, to)
fields.addField(R.string.email_subject, sub)
fields.addField(R.string.email_body, body)
return ParsedData(ParsedContentType.EMAIL, fields)
}
is VCardAction,
is VEventAction -> VTypeParser.parseMap(text).let { parsedMap ->
parsedMap.forEach { item ->
fields.addField(item.key, item.value.joinToString("\n") { it.value })
}
return ParsedData(
if (action is VCardAction) {
ParsedContentType.CONTACT_CARD
} else {
ParsedContentType.CALENDAR_EVENT
},
fields
)
}
is WebAction -> try {
text.toUri().run {
fields.addField(R.string.scheme, scheme)
fields.addField(R.string.host, host)
fields.addField(R.string.query, query)
}
return ParsedData(ParsedContentType.URL, fields)
} catch (_: Exception) {
// Ignore
}
is OtpauthAction -> try {
text.toUri().run {
val label = pathSegments.firstOrNull()?.let {
Uri.decode(it)
} ?: ""
val colonIdx = label.indexOf(':')
val issuerFromLabel = if (colonIdx >= 0) {
label.substring(0, colonIdx)
} else {
null
}
val account = if (colonIdx >= 0) {
label.substring(colonIdx + 1).trim()
} else {
label
}
val issuer = getQueryParameter("issuer") ?: issuerFromLabel
fields.addField(R.string.entry_type, host?.uppercase())
fields.addField(R.string.otp_account, account)
fields.addField(R.string.otp_issuer, issuer)
fields.addField(R.string.otp_algorithm, getQueryParameter("algorithm"))
fields.addField(R.string.otp_digits, getQueryParameter("digits"))
if (host == "totp") {
fields.addField(R.string.otp_period, getQueryParameter("period"))
} else {
fields.addField(R.string.otp_counter, getQueryParameter("counter"))
}
}
return ParsedData(ParsedContentType.OTP, fields)
} catch (_: Exception) {
// Ignore
}
is WifiAction -> WifiConnector.parseMap(text)?.let { wifiData ->
fields.addField(R.string.wifi_ssid, wifiData["S"])
fields.addField(R.string.wifi_password, wifiData["P"])
fields.addField(R.string.wifi_type, wifiData["T"])
fields.addField(R.string.wifi_hidden, wifiData["H"])
fields.addField(R.string.wifi_eap, wifiData["E"])
fields.addField(R.string.wifi_identity, wifiData["I"])
fields.addField(R.string.wifi_anonymous_identity, wifiData["A"])
fields.addField(R.string.wifi_phase2, wifiData["PH2"])
return ParsedData(ParsedContentType.WIFI_NETWORK, fields)
}
}
return ParsedData(ParsedContentType.UNKNOWN, emptyList())
}
fun parsedContentTypeFromName(
name: String?
): ParsedContentType = try {
ParsedContentType.valueOf(name ?: "")
} catch (_: IllegalArgumentException) {
ParsedContentType.UNKNOWN
}
private fun MutableList<ParsedField>.addField(
key: Any,
value: CharSequence?,
link: String? = null
) {
add(ParsedField(key, value, link))
}
@@ -0,0 +1,82 @@
package de.markusfisch.android.binaryeye.content
import de.markusfisch.android.zxingcpp.ZxingCpp
data class DeutschePostTrackingLink(
val label: String,
val link: String
)
object DeutschePostParser {
fun parse(
bytes: ByteArray,
format: String
): DeutschePostTrackingLink? {
var isStamp = false
var rawData = bytes
if (format == ZxingCpp.BarcodeFormat.DataMatrix.name &&
bytes.toString(Charsets.ISO_8859_1).startsWith("DEA5")
) {
if (bytes.size == 47) {
isStamp = true
} else if (bytes.size > 47) {
// Transform back to original data.
rawData = bytes.toString(Charsets.UTF_8).toByteArray(
Charsets.ISO_8859_1
)
if (rawData.size == 47) {
isStamp = true
}
}
}
if (!isStamp) {
return null
}
val hex = StringBuilder()
hex.append(String.format("%02X", rawData[9]))
hex.append(String.format("%02X", rawData[10]))
hex.append(String.format("%02X", rawData[11]))
hex.append(String.format("%02X", rawData[12]))
hex.append(String.format("%02X", rawData[13]))
hex.append(String.format("%X", (rawData[4].toInt() and 0x0f).toByte()))
hex.append(String.format("%02X", rawData[5]))
hex.append(String.format("%02X", rawData[6]))
hex.append(String.format("%02X", rawData[7]))
hex.append(String.format("%02X", rawData[8]))
val hexString = hex.toString()
val trackingNumber = hexString + String.format(
"%X",
crc4(hexString.toByteArray(Charsets.ISO_8859_1))
)
return DeutschePostTrackingLink(
trackingNumber,
"https://www.deutschepost.de/de/s/sendungsverfolgung.html?piececode=$trackingNumber",
)
}
// CRC-4 with polynomial x^4 + x + 1.
private fun crc4(input: ByteArray): Int {
var crc = 0
var i = 0
while (i < input.size) {
val c = input[i].toInt()
var j = 0x80
while (j != 0) {
var bit = crc and 0x8
crc = crc shl 1
if (c and j != 0) {
bit = bit xor 0x8
}
if (bit != 0) {
crc = crc xor 0x3
}
j = j ushr 1
}
++i
}
crc = crc and 0xF
return crc
}
}
@@ -6,6 +6,9 @@ import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.content.ParsedContentType
import de.markusfisch.android.binaryeye.content.parseData
import de.markusfisch.android.binaryeye.content.parsedContentTypeFromName
import de.markusfisch.android.binaryeye.preference.Preferences
import de.markusfisch.android.binaryeye.zxingcpp.migrateBarcodeFormatName
import de.markusfisch.android.binaryeye.zxingcpp.oldToNewFormatNames
@@ -13,9 +16,12 @@ import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
class Database {
private lateinit var db: SQLiteDatabase
private lateinit var context: Context
fun open(context: Context) {
this.context = context.applicationContext
db = OpenHelper(context).writableDatabase
fillMissingContentTypes()
}
fun getScans(query: String? = null): Cursor = db.rawQuery(
@@ -25,6 +31,7 @@ class Database {
$SCANS_DATETIME,
$SCANS_NAME,
$SCANS_TEXT,
$SCANS_CONTENT_TYPE,
$SCANS_FORMAT
FROM $SCANS
${getWhereClause(query)}
@@ -49,7 +56,8 @@ class Database {
$SCANS_GTIN_COUNTRY,
$SCANS_GTIN_ADD_ON,
$SCANS_GTIN_PRICE,
$SCANS_GTIN_ISSUE_NUMBER
$SCANS_GTIN_ISSUE_NUMBER,
$SCANS_CONTENT_TYPE
FROM $SCANS
${getWhereClause(query)}
ORDER BY $SCANS_PINNED DESC, $SCANS_DATETIME DESC
@@ -93,7 +101,8 @@ class Database {
$SCANS_GTIN_COUNTRY,
$SCANS_GTIN_ADD_ON,
$SCANS_GTIN_PRICE,
$SCANS_GTIN_ISSUE_NUMBER
$SCANS_GTIN_ISSUE_NUMBER,
$SCANS_CONTENT_TYPE
FROM $SCANS
WHERE $SCANS_ID IN (${ids.joinToString(",")})
ORDER BY $SCANS_PINNED DESC, $SCANS_DATETIME DESC
@@ -121,7 +130,8 @@ class Database {
$SCANS_GTIN_COUNTRY,
$SCANS_GTIN_ADD_ON,
$SCANS_GTIN_PRICE,
$SCANS_GTIN_ISSUE_NUMBER
$SCANS_GTIN_ISSUE_NUMBER,
$SCANS_CONTENT_TYPE
FROM $SCANS
WHERE $SCANS_ID = ?
""".trimMargin(), arrayOf("$id")
@@ -151,7 +161,8 @@ class Database {
it.getString(SCANS_DATETIME),
it.getLong(SCANS_ID),
it.getString(SCANS_NAME),
it.getInt(SCANS_PINNED) != 0
it.getInt(SCANS_PINNED) != 0,
parsedContentTypeFromName(it.getString(SCANS_CONTENT_TYPE))
)
} else {
null
@@ -208,6 +219,10 @@ class Database {
scan.price?.let { put(SCANS_GTIN_PRICE, it) }
scan.issueNumber?.let { put(SCANS_GTIN_ISSUE_NUMBER, it) }
put(SCANS_PINNED, if (scan.pinned) 1 else 0)
put(
SCANS_CONTENT_TYPE,
getContentType(scan.text, scan.raw, scan.format.name).name
)
}
)
}
@@ -279,8 +294,52 @@ class Database {
db.update(SCANS, cv, "$SCANS_ID = ?", arrayOf("$id"))
}
private fun fillMissingContentTypes() {
Thread {
db.rawQuery(
"""SELECT
$SCANS_ID,
$SCANS_TEXT,
$SCANS_RAW,
$SCANS_FORMAT
FROM $SCANS
WHERE $SCANS_CONTENT_TYPE IS NULL
""".trimMargin(),
null
).use {
while (it.moveToNext()) {
val id = it.getLong(SCANS_ID)
val type = getContentType(
it.getString(SCANS_TEXT),
it.getBlob(SCANS_RAW),
it.getString(SCANS_FORMAT)
)
db.update(
SCANS,
ContentValues().apply {
put(SCANS_CONTENT_TYPE, type.name)
},
"$SCANS_ID = ?",
arrayOf("$id")
)
}
}
}.start()
}
private fun getContentType(
text: String,
raw: ByteArray?,
format: String
) = parseData(
context,
text,
raw ?: text.toByteArray(),
format
).type
private class OpenHelper(context: Context) :
SQLiteOpenHelper(context, FILE_NAME, null, 10) {
SQLiteOpenHelper(context, FILE_NAME, null, 11) {
override fun onCreate(db: SQLiteDatabase) {
db.createScans()
}
@@ -317,6 +376,9 @@ class Database {
if (oldVersion < 10) {
db.addPinnedColumn()
}
if (oldVersion < 11) {
db.addContentTypeColumn()
}
}
}
@@ -351,6 +413,7 @@ class Database {
const val SCANS_GTIN_PRICE = "gtin_price"
const val SCANS_GTIN_ISSUE_NUMBER = "gtin_issue_number"
const val SCANS_PINNED = "pinned"
const val SCANS_CONTENT_TYPE = "content_type"
private fun SQLiteDatabase.createScans() {
execSQL("DROP TABLE IF EXISTS $SCANS".trimMargin())
@@ -375,7 +438,8 @@ class Database {
$SCANS_GTIN_ADD_ON TEXT,
$SCANS_GTIN_PRICE TEXT,
$SCANS_GTIN_ISSUE_NUMBER TEXT,
$SCANS_PINNED INTEGER NOT NULL DEFAULT 0
$SCANS_PINNED INTEGER NOT NULL DEFAULT 0,
$SCANS_CONTENT_TYPE TEXT NOT NULL DEFAULT '${ParsedContentType.UNKNOWN.name}'
)""".trimMargin()
)
}
@@ -446,5 +510,9 @@ class Database {
"ALTER TABLE $SCANS ADD COLUMN $SCANS_PINNED INTEGER NOT NULL DEFAULT 0"
)
}
private fun SQLiteDatabase.addContentTypeColumn() {
execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_CONTENT_TYPE TEXT")
}
}
}
@@ -2,6 +2,8 @@ package de.markusfisch.android.binaryeye.database
import android.os.Bundle
import android.text.format.DateFormat
import de.markusfisch.android.binaryeye.content.ParsedContentType
import de.markusfisch.android.binaryeye.content.parsedContentTypeFromName
import de.markusfisch.android.binaryeye.zxingcpp.migrateBarcodeFormatName
import de.markusfisch.android.zxingcpp.ZxingCpp.BarcodeFormat
import de.markusfisch.android.zxingcpp.ZxingCpp.BitMatrix
@@ -27,7 +29,8 @@ data class Scan(
val dateTime: String = getDateTime(),
var id: Long = 0L,
val label: String? = null,
val pinned: Boolean = false
val pinned: Boolean = false,
val contentType: ParsedContentType = ParsedContentType.UNKNOWN
) {
// Needs to be overwritten manually, as ByteArray is an array and
// this isn't handled well by Kotlin.
@@ -59,7 +62,8 @@ data class Scan(
price == other.price &&
issueNumber == other.issueNumber &&
label == other.label &&
pinned == other.pinned
pinned == other.pinned &&
contentType == other.contentType
}
// Needs to be overwritten manually, as ByteArray is an array and
@@ -82,6 +86,7 @@ data class Scan(
result = 31 * result + (issueNumber?.hashCode() ?: 0)
result = 31 * result + (label?.hashCode() ?: 0)
result = 31 * result + pinned.hashCode()
result = 31 * result + contentType.hashCode()
return result
}
@@ -133,6 +138,7 @@ fun Scan.toBundle() = Bundle().apply {
putLong(SCAN_ID, id)
putString(SCAN_LABEL, label)
putBoolean(SCAN_PINNED, pinned)
putString(SCAN_CONTENT_TYPE, contentType.name)
}
fun Bundle.toScan(): Scan? {
@@ -159,7 +165,8 @@ fun Bundle.toScan(): Scan? {
dateTime = getString(SCAN_DATE_TIME) ?: getDateTime(),
id = getLong(SCAN_ID, 0L),
label = getString(SCAN_LABEL),
pinned = getBoolean(SCAN_PINNED, false)
pinned = getBoolean(SCAN_PINNED, false),
contentType = parsedContentTypeFromName(getString(SCAN_CONTENT_TYPE))
)
} catch (_: IllegalArgumentException) {
null
@@ -207,3 +214,4 @@ private const val SCAN_DATE_TIME = "date_time"
private const val SCAN_ID = "id"
private const val SCAN_LABEL = "label"
private const val SCAN_PINNED = "pinned"
private const val SCAN_CONTENT_TYPE = "content_type"
+1 -1
View File
@@ -9,5 +9,5 @@
<string name="sha1">SHA1</string>
<string name="sha256">SHA256</string>
<string name="scan_meta_data">%1$s • %2$s</string>
<string name="parsed_type_deutsche_post">Deutsche Post Matrix Code</string>
<string name="parsed_type_deutsche_post">Deutsche Post Tracking Link</string>
</resources>