From e10a410788259b29131418b099e3657f0d9f9115 Mon Sep 17 00:00:00 2001 From: darthpaul Date: Sat, 18 Sep 2021 16:14:58 +0100 Subject: [PATCH] feat: restore mms --- .../smsmessenger/extensions/Context.kt | 26 --- .../smsmessenger/helpers/MessagesExporter.kt | 10 +- .../smsmessenger/helpers/MessagesImporter.kt | 16 +- .../smsmessenger/helpers/MessagesReader.kt | 75 ++++--- .../smsmessenger/helpers/MessagesWriter.kt | 210 +++++++++++++++--- .../smsmessenger/models/ExportedMessage.kt | 4 +- .../smsmessenger/models/MmsAddress.kt | 28 +++ .../smsmessenger/models/MmsBackup.kt | 73 ++++++ .../smsmessenger/models/MmsPart.kt | 60 +++++ .../smsmessenger/models/SmsBackup.kt | 78 ++++--- 10 files changed, 438 insertions(+), 142 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsAddress.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsBackup.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsPart.kt diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt index e72d6b27..a8dbf570 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt @@ -255,32 +255,6 @@ fun Context.getConversations(threadId: Long? = null, privateContacts: ArrayList< return conversations } -fun Context.getAllMessages(): List { - val uri = Sms.CONTENT_URI - val sortOrder = "${Sms._ID}" - - val messages= mutableListOf() - queryCursor(uri, null, null, null, sortOrder, showErrors = true) { cursor -> - val senderNumber = cursor.getStringValue(Sms.ADDRESS) - val id = cursor.getLongValue(Sms._ID) - val body = cursor.getStringValue(Sms.BODY) - val person = cursor.getStringValue(Sms.PERSON) - val protocol = cursor.getStringValue(Sms.PROTOCOL) - val type = cursor.getIntValue(Sms.TYPE) - val date = (cursor.getLongValue(Sms.DATE) / 1000) - val read = cursor.getIntValue(Sms.READ) - val thread = cursor.getLongValue(Sms.THREAD_ID) - val subscriptionId = cursor.getIntValue(Sms.SUBSCRIPTION_ID) - val status = cursor.getIntValue(Sms.STATUS) - - messages.add(SmsBackup(id, senderNumber, body, "", date, - 0, 0,0 , person, protocol, read, Any(), 0, 0, status, subscriptionId, Any(), thread, type)) - } - - return messages -} - - fun Context.getConversationIds(): List { val uri = Uri.parse("${Threads.CONTENT_URI}?simple=true") val projection = arrayOf(Threads._ID) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesExporter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesExporter.kt index 1cd4728e..627d9a6a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesExporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesExporter.kt @@ -42,28 +42,28 @@ class MessagesExporter(private val context: Context) { var written = 0 writer.beginObject() val conversationIds = context.getConversationIds() - for(threadId in conversationIds){ + for (threadId in conversationIds) { writer.name(threadId.toString()) writer.beginObject() writer.name("threadId") writer.value(threadId) - if(config.exportSms){ + if (config.exportSms) { writer.name("sms") writer.beginArray() //write all sms - messageReader.forEachSms(threadId){ + messageReader.forEachSms(threadId) { JsonObjectWriter(writer).write(it) written++ } writer.endArray() } - if(config.exportMms){ + if (config.exportMms) { writer.name("mms") writer.beginArray() //write all mms - messageReader.forEachMms(threadId){ + messageReader.forEachMms(threadId) { JsonObjectWriter(writer).write(it) written++ } diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesImporter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesImporter.kt index b8964774..6d9c218c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesImporter.kt @@ -1,6 +1,7 @@ package com.simplemobiletools.smsmessenger.helpers import android.content.Context +import android.net.Uri import android.provider.Telephony import android.util.Log import com.google.gson.Gson @@ -55,7 +56,7 @@ class MessagesImporter(private val context: Context) { // add mms if (config.importMms) { - message.sms.forEach(messageWriter::writeMmsMessage) + message.mms.forEach(messageWriter::writeMmsMessage) } // messageWriter.updateAllSmsThreads() @@ -66,9 +67,20 @@ class MessagesImporter(private val context: Context) { Log.w(TAG, "conversationIds = $conversationIds") context.queryCursor(Telephony.Sms.CONTENT_URI) { cursor -> val json = cursor.rowsToJson() - Log.w(TAG, "messages = $json") + Log.w(TAG, "smses = $json") } + context.queryCursor(Telephony.Mms.CONTENT_URI) { cursor -> + val json = cursor.rowsToJson() + Log.w(TAG, "mmses = $json") + } + + context.queryCursor(Uri.parse("content://mms/part")) { cursor -> + val json = cursor.rowsToJson() + Log.w(TAG, "parts = $json") + } + + refreshMessages() diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesReader.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesReader.kt index 33f48c63..4e602e1c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesReader.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesReader.kt @@ -6,12 +6,9 @@ import android.net.Uri import android.provider.Telephony import android.util.Base64 import android.util.Log -import com.google.android.mms.pdu_alt.PduHeaders import com.google.gson.JsonArray import com.google.gson.JsonObject -import com.simplemobiletools.commons.extensions.getIntValue -import com.simplemobiletools.commons.extensions.getStringValue -import com.simplemobiletools.commons.extensions.queryCursor +import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.isQPlus import com.simplemobiletools.smsmessenger.extensions.optLong import com.simplemobiletools.smsmessenger.extensions.optString @@ -23,38 +20,35 @@ class MessagesReader(private val context: Context) { companion object { private const val TAG = "MessagesReader" } + fun forEachSms(threadId: Long, block: (JsonObject) -> Unit) { - forEachThreadMessage(Telephony.Sms.CONTENT_URI, threadId, block) - } - - fun forEachMms(threadId: Long, includeAttachment: Boolean = true, block: (JsonObject) -> Unit) { - forEachThreadMessage(Telephony.Mms.CONTENT_URI, threadId) { obj -> - if (includeAttachment) { - obj.add("parts", getParts(obj.getAsJsonPrimitive("_id").asLong)) - } - obj.add(Telephony.CanonicalAddressesColumns.ADDRESS, getMMSAddresses(obj.getAsJsonPrimitive("_id").asLong)) - block(obj) - } - } - - private fun forEachThreadMessage(contentUri: Uri, threadId: Long, block: (JsonObject) -> Unit) { val selection = "${Telephony.Sms.THREAD_ID} = ?" val selectionArgs = arrayOf(threadId.toString()) - context.queryCursor(contentUri, null, selection, selectionArgs) { cursor -> + context.queryCursor(Telephony.Sms.CONTENT_URI, null, selection, selectionArgs) { cursor -> val json = cursor.rowsToJson() - forceMillisDate(json, "date") - forceMillisDate(json, "date_sent") block(json) } } - private fun forceMillisDate(message: JsonObject, field: String) { - /* sometimes the sms are in millis and the mms in secs... */ - if (message.get(field).isJsonPrimitive) { - val value = message.get(field).optLong - if (value != null && value != 0L && value < 500000000000L) { // 500000000000 = Tuesday, 5 November 1985 00:53:20 GMT - message.addProperty(field, value * 1000) - } + // all mms from simple sms are non-text messages + fun forEachMms(threadId: Long, includeTextOnlyAttachment: Boolean = false, block: (JsonObject) -> Unit) { + + val selection = if (includeTextOnlyAttachment) { + "${Telephony.Mms.THREAD_ID} = ? AND ${Telephony.Mms.TEXT_ONLY} = ?" + } else { + "${Telephony.Mms.THREAD_ID} = ?" + } + + val selectionArgs = if (includeTextOnlyAttachment) { + arrayOf(threadId.toString(), "1") + } else { + arrayOf(threadId.toString()) + } + context.queryCursor(Telephony.Mms.CONTENT_URI, null, selection, selectionArgs) { cursor -> + val json = cursor.rowsToJson() + json.add("parts", getParts(json.getAsJsonPrimitive(Telephony.Mms._ID).asLong)) + json.add("addresses", getMMSAddresses(json.getAsJsonPrimitive(Telephony.Mms._ID).asLong)) + block(json) } } @@ -67,8 +61,9 @@ class MessagesReader(private val context: Context) { Uri.parse("content://mms/part") } - val selection = "${Telephony.Mms.Part.MSG_ID}=$mmsId" - context.queryCursor(uri, emptyArray(), selection) { cursor -> + val selection = "${Telephony.Mms.Part.MSG_ID}= ?" + val selectionArgs = arrayOf(mmsId.toString()) + context.queryCursor(uri, null, selection, selectionArgs) { cursor -> val part = cursor.rowsToJson() val hasTextValue = (part.has(Telephony.Mms.Part.TEXT) && !part.get(Telephony.Mms.Part.TEXT).optString.isNullOrEmpty()) @@ -85,7 +80,12 @@ class MessagesReader(private val context: Context) { } else -> { part.addProperty(MMS_CONTENT, usePart(part.get(Telephony.Mms.Part._ID).asLong) { stream -> - Base64.encodeToString(stream.readBytes(), Base64.DEFAULT) + val arr = stream.readBytes() + Log.d(TAG, "getParts: $arr") + Log.d(TAG, "getParts: size = ${arr.size}") + Log.d(TAG, "getParts: US_ASCII-> ${arr.toString(Charsets.US_ASCII)}") + Log.d(TAG, "getParts: UTF_8-> ${arr.toString(Charsets.UTF_8)}") + Base64.encodeToString(arr, Base64.DEFAULT) }) } } @@ -129,12 +129,15 @@ class MessagesReader(private val context: Context) { } val projection = arrayOf(Telephony.Mms.Addr.ADDRESS, Telephony.Mms.Addr.TYPE) - val selection = "${Telephony.Mms.Addr.MSG_ID}=$messageId" + val selection = "${Telephony.Mms.Addr.MSG_ID}= ?" + val selectionArgs = arrayOf(messageId.toString()) - context.queryCursor(addressUri, projection, selection) { cursor -> - when (cursor.getIntValue(Telephony.Mms.Addr.TYPE)) { - PduHeaders.FROM, PduHeaders.TO, PduHeaders.CC, PduHeaders.BCC -> jsonArray.add(cursor.getStringValue(Telephony.Mms.Addr.ADDRESS)) - } + context.queryCursor(addressUri, null, selection, selectionArgs) { cursor -> + val part = cursor.rowsToJson() + jsonArray.add(part) +// when (cursor.getIntValue(Telephony.Mms.Addr.TYPE)) { +// PduHeaders.FROM, PduHeaders.TO, PduHeaders.CC, PduHeaders.BCC -> jsonArray.add(cursor.getStringValue(Telephony.Mms.Addr.ADDRESS)) +// } } return jsonArray diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesWriter.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesWriter.kt index f6ff3f0b..0c17db15 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesWriter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesWriter.kt @@ -1,29 +1,40 @@ package com.simplemobiletools.smsmessenger.helpers +import android.annotation.SuppressLint import android.content.ContentValues import android.content.Context +import android.net.Uri import android.provider.Telephony +import android.provider.Telephony.Mms +import android.util.Base64 import android.util.Log +import com.google.android.mms.pdu_alt.PduHeaders +import com.klinker.android.send_message.Utils +import com.simplemobiletools.commons.extensions.getLongValue import com.simplemobiletools.commons.extensions.queryCursor +import com.simplemobiletools.commons.helpers.isQPlus import com.simplemobiletools.smsmessenger.extensions.getThreadId -import com.simplemobiletools.smsmessenger.extensions.toContentValues +import com.simplemobiletools.smsmessenger.models.MmsAddress +import com.simplemobiletools.smsmessenger.models.MmsBackup +import com.simplemobiletools.smsmessenger.models.MmsPart +import com.simplemobiletools.smsmessenger.models.SmsBackup +import java.nio.charset.Charset class MessagesWriter(private val context: Context) { companion object { + private const val INVALID_ID = -1L private const val TAG = "MessagesWriter" } private val contentResolver = context.contentResolver - fun writeSmsMessage(map: Map) { - Log.w(TAG, "writeSmsMessage: map=$map") - val contentValues = map.toContentValues() - contentValues.remove(Telephony.Sms._ID) - replaceThreadId(contentValues) + fun writeSmsMessage(smsBackup: SmsBackup) { + Log.w(TAG, "writeSmsMessage: smsBackup=$smsBackup") + val contentValues = smsBackup.toContentValues() + replaceSmsThreadId(contentValues) Log.d(TAG, "writeSmsMessage: contentValues=$contentValues") - val type = contentValues.getAsInteger(Telephony.Sms.TYPE) - Log.d(TAG, "writeSmsMessage: type=$type") - if ((type == Telephony.Sms.MESSAGE_TYPE_INBOX || type == Telephony.Sms.MESSAGE_TYPE_SENT) && !smsExist(map)) { + Log.d(TAG, "writeSmsMessage: type=${smsBackup.type}") + if ((smsBackup.type == Telephony.Sms.MESSAGE_TYPE_INBOX || smsBackup.type == Telephony.Sms.MESSAGE_TYPE_SENT) && !smsExist(smsBackup)) { Log.d(TAG, "writeSmsMessage: Inserting SMS...") val uri = Telephony.Sms.CONTENT_URI Log.d(TAG, "writeSmsMessage: uri=$uri") @@ -31,21 +42,17 @@ class MessagesWriter(private val context: Context) { } } - private fun replaceThreadId(contentValues: ContentValues) { + private fun replaceSmsThreadId(contentValues: ContentValues) { val address = contentValues.get(Telephony.Sms.ADDRESS) - val threadId = context.getThreadId(address.toString()) + val threadId = Utils.getOrCreateThreadId(context, address.toString()) contentValues.put(Telephony.Sms.THREAD_ID, threadId) } - fun writeMmsMessage(map: Map) { - - } - - private fun smsExist(sms: Map): Boolean { + private fun smsExist(smsBackup: SmsBackup): Boolean { val uri = Telephony.Sms.CONTENT_URI val projection = arrayOf(Telephony.Sms._ID) val selection = "${Telephony.Sms.DATE} = ? AND ${Telephony.Sms.ADDRESS} = ? AND ${Telephony.Sms.TYPE} = ?" - val selectionArgs = arrayOf(sms[Telephony.Sms.DATE] as String, sms[Telephony.Sms.ADDRESS] as String, sms[Telephony.Sms.TYPE] as String) + val selectionArgs = arrayOf(smsBackup.date.toString(), smsBackup.address, smsBackup.type.toString()) var exists = false context.queryCursor(uri, projection, selection, selectionArgs) { @@ -57,26 +64,159 @@ class MessagesWriter(private val context: Context) { return exists } - private fun Map.toSmsContentValues(): ContentValues { - val values = ContentValues() - val address = get(Telephony.Sms.ADDRESS) as String - values.put(Telephony.Sms.ADDRESS, address) - values.put(Telephony.Sms.DATE, get(Telephony.Sms.DATE)) - values.put(Telephony.Sms.DATE_SENT, get(Telephony.Sms.DATE_SENT)) - values.put(Telephony.Sms.BODY, get(Telephony.Sms.BODY) as String) - values.put(Telephony.Sms.TYPE, get(Telephony.Sms.TYPE)) - values.put(Telephony.Sms.PROTOCOL, get(Telephony.Sms.PROTOCOL)) - values.put(Telephony.Sms.SERVICE_CENTER, get(Telephony.Sms.SERVICE_CENTER)) - values.put(Telephony.Sms.STATUS, get(Telephony.Sms.STATUS)) - values.put(Telephony.Sms.READ, get(Telephony.Sms.READ)) - values.put(Telephony.Sms.CREATOR, get(Telephony.Sms.CREATOR)) - values.put(Telephony.Sms.LOCKED, get(Telephony.Sms.LOCKED)) - values.put(Telephony.Sms.SUBSCRIPTION_ID, get(Telephony.Sms.SUBSCRIPTION_ID)) - values.put(Telephony.Sms.THREAD_ID, context.getThreadId(address)) - return values + fun writeMmsMessage(mmsBackup: MmsBackup) { + // 1. write mms msg, get the msg_id, check if mms exists before writing + // 2. write parts - parts depend on the msg id, check if part exist before writing, store _data in Downloads directory + // 3. write the addresses, address depends on msg id too, check if address exist before writing + Log.w(TAG, "writeMmsMessage: backup=$mmsBackup") + val contentValues = mmsBackup.toContentValues() + val threadId = getMmsThreadId(mmsBackup) + if (threadId != INVALID_ID) { + contentValues.put(Telephony.Mms.THREAD_ID, threadId) + Log.w(TAG, "writeMmsMessage: backup=$mmsBackup") + //write mms + if ((mmsBackup.messageBox == Telephony.Mms.MESSAGE_BOX_INBOX || mmsBackup.messageBox == Telephony.Mms.MESSAGE_BOX_SENT) && !mmsExist(mmsBackup)) { + contentResolver.insert(Telephony.Mms.CONTENT_URI, contentValues) + } + + val messageId = getMmsId(mmsBackup) + if (messageId != INVALID_ID) { + Log.d(TAG, "writing mms addresses") + //write addresses + mmsBackup.addresses.forEach { writeMmsAddress(it, messageId) } + mmsBackup.mmsParts.forEach { writeMmsPart(it, messageId) } + } else { + Log.d(TAG, "failed to write mms message, invalid mms id") + } + } else { + Log.d(TAG, "failed to write mms message, invalid thread id") + } } - fun updateAllSmsThreads(){ + private fun getMmsThreadId(mmsBackup: MmsBackup): Long { + val address = when (mmsBackup.messageBox) { + Mms.MESSAGE_BOX_INBOX -> { + mmsBackup.addresses.firstOrNull { it.type == PduHeaders.FROM }?.address + } + else -> { + mmsBackup.addresses.firstOrNull { it.type == PduHeaders.TO }?.address + } + } + return if (!address.isNullOrEmpty()) { + Utils.getOrCreateThreadId(context, address) + } else { + INVALID_ID + } + } + + private fun getMmsId(mmsBackup: MmsBackup): Long { + val threadId = getMmsThreadId(mmsBackup) + val uri = Telephony.Mms.CONTENT_URI + val projection = arrayOf(Telephony.Mms._ID) + val selection = "${Telephony.Mms.DATE} = ? AND ${Telephony.Mms.DATE_SENT} = ? AND ${Telephony.Mms.THREAD_ID} = ? AND ${Telephony.Mms.MESSAGE_BOX} = ?" + val selectionArgs = arrayOf(mmsBackup.date.toString(), mmsBackup.dateSent.toString(), threadId.toString(), mmsBackup.messageBox.toString()) + + var id = INVALID_ID + context.queryCursor(uri, projection, selection, selectionArgs) { + id = it.getLongValue(Telephony.Mms._ID) + Log.i(TAG, "getMmsId After: $id") + } + Log.i(TAG, "getMmsId: $id") + + return id + } + + private fun mmsExist(mmsBackup: MmsBackup): Boolean { + return getMmsId(mmsBackup) != INVALID_ID + } + + @SuppressLint("NewApi") + private fun mmsAddressExist(mmsAddress: MmsAddress, messageId: Long): Boolean { + val addressUri = if (isQPlus()) { + Telephony.Mms.Addr.getAddrUriForMessage(messageId.toString()) + } else { + Uri.parse("content://mms/$messageId/addr") + } + val projection = arrayOf(Telephony.Mms.Addr._ID) + val selection = "${Telephony.Mms.Addr.TYPE} = ? AND ${Telephony.Mms.Addr.ADDRESS} = ? AND ${Telephony.Mms.Addr.MSG_ID} = ?" + val selectionArgs = arrayOf(mmsAddress.type.toString(), mmsAddress.address.toString(), messageId.toString()) + + var exists = false + context.queryCursor(addressUri, projection, selection, selectionArgs) { + exists = it.count > 0 + Log.i(TAG, "mmsAddressExist After: $exists") + } + Log.i(TAG, "mmsAddressExist: $exists") + + return exists + } + + @SuppressLint("NewApi") + private fun writeMmsAddress(mmsAddress: MmsAddress, messageId: Long) { + if (!mmsAddressExist(mmsAddress, messageId)) { + val addressUri = if (isQPlus()) { + Telephony.Mms.Addr.getAddrUriForMessage(messageId.toString()) + } else { + Uri.parse("content://mms/$messageId/addr") + } + + val contentValues = mmsAddress.toContentValues() + contentValues.put(Telephony.Mms.Addr.MSG_ID, messageId) + contentResolver.insert(addressUri, contentValues) + } else { + Log.w(TAG, "writeMmsAddress: Skip already exists") + } + } + + @SuppressLint("NewApi") + private fun writeMmsPart(mmsPart: MmsPart, messageId: Long) { + Log.d(TAG, "writeMmsPart: Writing part= $mmsPart") + if (!mmsPartExist(mmsPart, messageId)) { + val uri = Uri.parse("content://mms/${messageId}/part") + val contentValues = mmsPart.toContentValues() + contentValues.put(Mms.Part.MSG_ID, messageId) + val partUri = contentResolver.insert(uri, contentValues) + //write data + Log.d(TAG, "writeMmsPart: Inserted part=$partUri") + try { + if (partUri != null) { + if (mmsPart.isNonText()) { + contentResolver.openOutputStream(partUri).use { + val arr = Base64.decode(mmsPart.mmsContent, Base64.DEFAULT) + it!!.write(arr) + Log.d(TAG, "Wrote part data $mmsPart") + } + } else { + Log.w(TAG, "skip writing text content") + } + + } else { + Log.e(TAG, "invalid uri while writing part") + } + } catch (e: Exception) { + Log.e(TAG, "writeMmsPart: uri", e) + } + } + } + + @SuppressLint("NewApi") + private fun mmsPartExist(mmsPart: MmsPart, messageId: Long): Boolean { + val uri = Uri.parse("content://mms/${messageId}/part") + val projection = arrayOf(Telephony.Mms.Part._ID) + val selection = + "${Telephony.Mms.Part.CONTENT_LOCATION} = ? AND ${Telephony.Mms.Part.CT_TYPE} = ? AND ${Mms.Part.MSG_ID} = ? AND ${Telephony.Mms.Part.CONTENT_ID} = ?" + val selectionArgs = arrayOf(mmsPart.contentLocation.toString(), mmsPart.contentType.toString(), messageId.toString(), mmsPart.contentId.toString()) + var exists = false + context.queryCursor(uri, projection, selection, selectionArgs) { + exists = it.count > 0 + Log.i(TAG, "mmsPartExist After: $exists") + } + Log.i(TAG, "mmsPartExist: $exists") + + return exists + } + + fun updateAllSmsThreads() { // thread dates + states might be wrong, we need to force a full update // unfortunately there's no direct way to do that in the SDK, but passing a // negative conversation id to delete should to the trick diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ExportedMessage.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ExportedMessage.kt index fbf52d69..7239d813 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ExportedMessage.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ExportedMessage.kt @@ -6,7 +6,7 @@ data class ExportedMessage( @SerializedName("threadId") val threadId: Long, @SerializedName("sms") - val sms: List>, + val sms: List, @SerializedName("mms") - val mms: List>, + val mms: List, ) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsAddress.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsAddress.kt new file mode 100644 index 00000000..687dabf3 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsAddress.kt @@ -0,0 +1,28 @@ +package com.simplemobiletools.smsmessenger.models + +import android.content.ContentValues +import android.provider.Telephony +import androidx.core.content.contentValuesOf +import com.google.gson.annotations.SerializedName + +data class MmsAddress( + @SerializedName("address") + val address: String, + @SerializedName("msg_id") + val msgId: Int, + @SerializedName("type") + val type: Int, + @SerializedName("charset") + val charset: Int +) { + + fun toContentValues(): ContentValues { + // msgId would be added at the point of insertion + // because it may have changed + return contentValuesOf( + Telephony.Mms.Addr.ADDRESS to address, + Telephony.Mms.Addr.TYPE to type, + Telephony.Mms.Addr.CHARSET to charset, + ) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsBackup.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsBackup.kt new file mode 100644 index 00000000..e1ed2aac --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsBackup.kt @@ -0,0 +1,73 @@ +package com.simplemobiletools.smsmessenger.models + +import android.content.ContentValues +import android.provider.Telephony +import androidx.core.content.contentValuesOf +import com.google.gson.annotations.SerializedName + +data class MmsBackup( + @SerializedName("creator") + val creator: String?, + @SerializedName("ct_t") + val contentType: String?, + @SerializedName("d_rpt") + val deliveryReport: Int, + @SerializedName("date") + val date: Long, + @SerializedName("date_sent") + val dateSent: Long, + @SerializedName("locked") + val locked: Int, + @SerializedName("m_id") + val messageId: String?, + @SerializedName("m_type") + val messageType: Int, + @SerializedName("msg_box") + val messageBox: Int, + @SerializedName("read") + val read: Int, + @SerializedName("rr") + val readReport: Int, + @SerializedName("seen") + val seen: Int, + @SerializedName("text_only") + val textOnly: Int, + @SerializedName("st") + val status: String?, + @SerializedName("sub") + val subject: String?, + @SerializedName("sub_cs") + val subjectCharSet: String?, + @SerializedName("sub_id") + val subscriptionId: Long, + @SerializedName("thread_id") + val threadId: Long, + @SerializedName("tr_id") + val transactionId: String?, + @SerializedName("addresses") + val addresses: List, + @SerializedName("parts") + val mmsParts: List, +) { + + fun toContentValues(): ContentValues { + return contentValuesOf( + Telephony.Mms.TRANSACTION_ID to transactionId, + Telephony.Mms.SUBSCRIPTION_ID to subscriptionId, + Telephony.Mms.SUBJECT to subject, + Telephony.Mms.DATE to date, + Telephony.Mms.DATE_SENT to dateSent, + Telephony.Mms.LOCKED to locked, + Telephony.Mms.READ to read, + Telephony.Mms.STATUS to status, + Telephony.Mms.SUBJECT_CHARSET to subjectCharSet, + Telephony.Mms.SEEN to seen, + Telephony.Mms.MESSAGE_TYPE to messageType, + Telephony.Mms.MESSAGE_BOX to messageBox, + Telephony.Mms.DELIVERY_REPORT to deliveryReport, + Telephony.Mms.READ_REPORT to readReport, + Telephony.Mms.CONTENT_TYPE to contentType, + Telephony.Mms.TEXT_ONLY to textOnly, + ) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsPart.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsPart.kt new file mode 100644 index 00000000..b63f4eaf --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsPart.kt @@ -0,0 +1,60 @@ +package com.simplemobiletools.smsmessenger.models + +import android.content.ContentValues +import android.provider.Telephony +import androidx.core.content.contentValuesOf +import com.google.gson.annotations.SerializedName + +data class MmsPart( + @SerializedName("cd") + val contentDisposition: String?, + @SerializedName("chset") + val charset: String?, + @SerializedName("cid") + val contentId: String?, + @SerializedName("cl") + val contentLocation: String?, + @SerializedName("ct") + val contentType: String, + @SerializedName("ctt_s") + val ctStart: String?, + @SerializedName("ctt_t") + val ctType: String?, + @SerializedName("_data") + val `data`: String?, + @SerializedName("fn") + val filename: String?, + @SerializedName("_id") + val id: Long, + @SerializedName("mid") + val messageId: Long, + @SerializedName("name") + val name: String, + @SerializedName("seq") + val sequenceOrder: Int, + @SerializedName("text") + val text: String?, + @SerializedName("mms_content") + val mmsContent: String?, +) { + + fun toContentValues(): ContentValues { + return contentValuesOf( + Telephony.Mms.Part.CONTENT_DISPOSITION to contentDisposition, + Telephony.Mms.Part.CHARSET to charset, + Telephony.Mms.Part.CONTENT_ID to contentId, + Telephony.Mms.Part.CONTENT_LOCATION to contentLocation, + Telephony.Mms.Part.CONTENT_TYPE to contentType, + Telephony.Mms.Part.CT_START to ctStart, + Telephony.Mms.Part.CT_TYPE to ctType, + Telephony.Mms.Part.FILENAME to filename, + Telephony.Mms.Part.NAME to name, + Telephony.Mms.Part.SEQ to sequenceOrder, + Telephony.Mms.Part.TEXT to text, + ) + } + + fun isNonText(): Boolean { + return !(text != null || contentType.lowercase().startsWith("text") || contentType.lowercase() == "application/smil") + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/SmsBackup.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/SmsBackup.kt index 2e7fd5e7..da289a2a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/SmsBackup.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/SmsBackup.kt @@ -1,46 +1,52 @@ package com.simplemobiletools.smsmessenger.models +import android.content.ContentValues import android.provider.Telephony +import androidx.core.content.contentValuesOf import com.google.gson.annotations.SerializedName data class SmsBackup( - @SerializedName(Telephony.Sms._ID) - val id: Long, - @SerializedName(Telephony.Sms.ADDRESS) - val address: String, - @SerializedName(Telephony.Sms.BODY) - val body: String, - @SerializedName(Telephony.Sms.CREATOR) - val creator: String, - @SerializedName(Telephony.Sms.DATE) - val date: Long, - @SerializedName(Telephony.Sms.DATE_SENT) - val dateSent: Int, - @SerializedName(Telephony.Sms.ERROR_CODE) - val errorCode: Int, - @SerializedName(Telephony.Sms.LOCKED) - val locked: Int, - @SerializedName(Telephony.Sms.PERSON) - val person: String, - @SerializedName(Telephony.Sms.PROTOCOL) - val protocol: String, - @SerializedName("read") - val read: Int, - @SerializedName("reply_path_present") - val replyPathPresent: Any, - @SerializedName("seen") - val seen: Int, - @SerializedName("service_center") - val serviceCenter: Any, - @SerializedName("status") - val status: Int, - @SerializedName("sub_id") - val subId: Int, - @SerializedName("subject") - val subject: Any, @SerializedName("thread_id") val threadId: Long, + @SerializedName("sub_id") + val subscriptionId: Long, + @SerializedName("address") + val address: String, + @SerializedName("body") + val body: String, + @SerializedName("date") + val date: Long, + @SerializedName("date_sent") + val dateSent: Long, + @SerializedName("locked") + val locked: Int, + @SerializedName("protocol") + val protocol: String?, + @SerializedName("read") + val read: Int, + @SerializedName("status") + val status: Int, @SerializedName("type") - val type: Int -) + val type: Int, + @SerializedName("service_center") + val serviceCenter: String? +) { + + fun toContentValues(): ContentValues { + return contentValuesOf( + Telephony.Sms.THREAD_ID to threadId, + Telephony.Sms.SUBSCRIPTION_ID to subscriptionId, + Telephony.Sms.ADDRESS to address, + Telephony.Sms.BODY to body, + Telephony.Sms.DATE to date, + Telephony.Sms.DATE_SENT to dateSent, + Telephony.Sms.LOCKED to locked, + Telephony.Sms.PROTOCOL to protocol, + Telephony.Sms.READ to read, + Telephony.Sms.STATUS to status, + Telephony.Sms.TYPE to type, + Telephony.Sms.SERVICE_CENTER to serviceCenter, + ) + } +}