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 a8dbf570..1e2479d0 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/extensions/Context.kt @@ -260,7 +260,7 @@ fun Context.getConversationIds(): List { val projection = arrayOf(Threads._ID) val selection = "${Threads.MESSAGE_COUNT} > ?" val selectionArgs = arrayOf("0") - val sortOrder = "${Threads.DATE} DESC" + val sortOrder = "${Threads.DATE} ASC" val conversationIds = mutableListOf() queryCursor(uri, projection, selection, selectionArgs, sortOrder, true) { cursor -> val id = cursor.getLongValue(Threads._ID) diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt index b7e92d21..7e99610e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt @@ -22,7 +22,6 @@ const val EXPORT_MIME_TYPE = "application/json" const val EXPORT_FILE_EXT = ".json" const val IMPORT_SMS = "import_sms" const val IMPORT_MMS = "import_mms" -const val MMS_CONTENT = "mms_content" private const val PATH = "com.simplemobiletools.smsmessenger.action." const val MARK_AS_READ = PATH + "mark_as_read" 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 627d9a6a..8251b871 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesExporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesExporter.kt @@ -1,6 +1,7 @@ package com.simplemobiletools.smsmessenger.helpers import android.content.Context +import com.google.gson.Gson import com.google.gson.stream.JsonWriter import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.smsmessenger.extensions.config @@ -14,6 +15,7 @@ class MessagesExporter(private val context: Context) { private val config = context.config private val messageReader = MessagesReader(context) + private val gson = Gson() fun exportMessages( outputStream: OutputStream?, @@ -27,33 +29,33 @@ class MessagesExporter(private val context: Context) { /* * We should have json in this format - * { - * "threadId" : { - * "threadId": "" + * [ + * { * "sms": [{ smses }], * "mms": [{ mmses }] - * } - * } + * }, + * { + * "sms": [{ smses }], + * "mms": [{ mmses }] + * } + * ] * * */ val writer = JsonWriter(outputStream.bufferedWriter()) writer.use { try { var written = 0 - writer.beginObject() + writer.beginArray() val conversationIds = context.getConversationIds() + val totalMessages = messageReader.getMessagesCount() for (threadId in conversationIds) { - writer.name(threadId.toString()) - writer.beginObject() - writer.name("threadId") - writer.value(threadId) if (config.exportSms) { writer.name("sms") writer.beginArray() //write all sms messageReader.forEachSms(threadId) { - JsonObjectWriter(writer).write(it) + writer.jsonValue(gson.toJson(it)) written++ } writer.endArray() @@ -64,7 +66,7 @@ class MessagesExporter(private val context: Context) { writer.beginArray() //write all mms messageReader.forEachMms(threadId) { - JsonObjectWriter(writer).write(it) + writer.jsonValue(gson.toJson(it)) written++ } @@ -73,7 +75,7 @@ class MessagesExporter(private val context: Context) { writer.endObject() } - writer.endObject() + writer.endArray() callback.invoke(ExportResult.EXPORT_OK) } catch (e: Exception) { callback.invoke(ExportResult.EXPORT_FAIL) 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 6d9c218c..b155cdc8 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesImporter.kt @@ -3,6 +3,7 @@ package com.simplemobiletools.smsmessenger.helpers import android.content.Context import android.net.Uri import android.provider.Telephony +import android.provider.Telephony.* import android.util.Log import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -32,9 +33,6 @@ class MessagesImporter(private val context: Context) { return@ensureBackgroundThread } - //read data from path - // parse json - // write data to sql db val inputStream = if (path.contains("/")) { File(path).inputStream() } else { @@ -44,33 +42,32 @@ class MessagesImporter(private val context: Context) { inputStream.bufferedReader().use { try { val json = it.readText() - Log.d(TAG, "importMessages: json== ${json.length}") - val type = object : TypeToken>() {}.type - val data = gson.fromJson>(json, type) - Log.d(TAG, "importMessages: ${data.size}") - for (message in data.values) { + Log.d(TAG, "importMessages: json== $json") + val type = object : TypeToken>() {}.type + val messages = gson.fromJson>(json, type) + Log.d(TAG, "importMessages: ${messages.size}") + for (message in messages) { // add sms if (config.importSms) { message.sms.forEach(messageWriter::writeSmsMessage) } - // add mms if (config.importMms) { message.mms.forEach(messageWriter::writeMmsMessage) } -// messageWriter.updateAllSmsThreads() - val conversations = context.getConversations() - val conversationIds = context.getConversationIds() - Log.w(TAG, "conversations = $conversations") - Log.w(TAG, "conversationIds = $conversationIds") - context.queryCursor(Telephony.Sms.CONTENT_URI) { cursor -> + context.queryCursor(Threads.CONTENT_URI) { cursor -> + val json = cursor.rowsToJson() + Log.w(TAG, "converations = $json") + } + + context.queryCursor(Sms.CONTENT_URI) { cursor -> val json = cursor.rowsToJson() Log.w(TAG, "smses = $json") } - context.queryCursor(Telephony.Mms.CONTENT_URI) { cursor -> + context.queryCursor(Mms.CONTENT_URI) { cursor -> val json = cursor.rowsToJson() Log.w(TAG, "mmses = $json") } @@ -80,10 +77,8 @@ class MessagesImporter(private val context: Context) { Log.w(TAG, "parts = $json") } - refreshMessages() - - + callback.invoke(ImportResult.IMPORT_OK) } } catch (e: Exception) { Log.e(TAG, "importMessages: ", e) 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 4e602e1c..0d02c1be 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesReader.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesReader.kt @@ -3,16 +3,16 @@ package com.simplemobiletools.smsmessenger.helpers import android.annotation.SuppressLint import android.content.Context import android.net.Uri -import android.provider.Telephony +import android.provider.Telephony.* import android.util.Base64 import android.util.Log -import com.google.gson.JsonArray -import com.google.gson.JsonObject import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.isQPlus -import com.simplemobiletools.smsmessenger.extensions.optLong -import com.simplemobiletools.smsmessenger.extensions.optString import com.simplemobiletools.smsmessenger.extensions.rowsToJson +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.io.IOException import java.io.InputStream @@ -21,22 +21,63 @@ class MessagesReader(private val context: Context) { private const val TAG = "MessagesReader" } - fun forEachSms(threadId: Long, block: (JsonObject) -> Unit) { - val selection = "${Telephony.Sms.THREAD_ID} = ?" + fun forEachSms(threadId: Long, block: (SmsBackup) -> Unit) { + val projection = arrayOf( + Sms.SUBSCRIPTION_ID, + Sms.ADDRESS, + Sms.BODY, + Sms.DATE, + Sms.DATE_SENT, + Sms.LOCKED, + Sms.PROTOCOL, + Sms.READ, + Sms.STATUS, + Sms.TYPE, + Sms.SERVICE_CENTER + ) + val selection = "${Sms.THREAD_ID} = ?" val selectionArgs = arrayOf(threadId.toString()) - context.queryCursor(Telephony.Sms.CONTENT_URI, null, selection, selectionArgs) { cursor -> - val json = cursor.rowsToJson() - block(json) + context.queryCursor(Sms.CONTENT_URI, projection, selection, selectionArgs) { cursor -> + val subscriptionId = cursor.getLongValue(Sms.SUBSCRIPTION_ID) + val address = cursor.getStringValue(Sms.ADDRESS) + val body = cursor.getStringValueOrNull(Sms.BODY) + val date = cursor.getLongValue(Sms.DATE) + val dateSent = cursor.getLongValue(Sms.DATE_SENT) + val locked = cursor.getIntValue(Sms.DATE_SENT) + val protocol = cursor.getStringValueOrNull(Sms.PROTOCOL) + val read = cursor.getIntValue(Sms.READ) + val status = cursor.getIntValue(Sms.STATUS) + val type = cursor.getIntValue(Sms.TYPE) + val serviceCenter = cursor.getStringValueOrNull(Sms.SERVICE_CENTER) + block(SmsBackup(subscriptionId, address, body, date, dateSent, locked, protocol, read, status, type, serviceCenter)) } } // all mms from simple sms are non-text messages - fun forEachMms(threadId: Long, includeTextOnlyAttachment: Boolean = false, block: (JsonObject) -> Unit) { - + fun forEachMms(threadId: Long, includeTextOnlyAttachment: Boolean = false, block: (MmsBackup) -> Unit) { + val projection = arrayOf( + Mms._ID, + Mms.CREATOR, + Mms.CONTENT_TYPE, + Mms.DELIVERY_REPORT, + Mms.DATE, + Mms.DATE_SENT, + Mms.LOCKED, + Mms.MESSAGE_TYPE, + Mms.MESSAGE_BOX, + Mms.READ, + Mms.READ_REPORT, + Mms.SEEN, + Mms.TEXT_ONLY, + Mms.STATUS, + Mms.SUBJECT_CHARSET, + Mms.SUBSCRIPTION_ID, + Mms.TRANSACTION_ID + ) val selection = if (includeTextOnlyAttachment) { - "${Telephony.Mms.THREAD_ID} = ? AND ${Telephony.Mms.TEXT_ONLY} = ?" + "${Mms.THREAD_ID} = ? AND ${Mms.TEXT_ONLY} = ?" } else { - "${Telephony.Mms.THREAD_ID} = ?" + "${Mms.THREAD_ID} = ?" } val selectionArgs = if (includeTextOnlyAttachment) { @@ -44,61 +85,95 @@ class MessagesReader(private val context: Context) { } 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) + context.queryCursor(Mms.CONTENT_URI, projection, selection, selectionArgs) { cursor -> + val mmsId = cursor.getLongValue(Mms._ID) + val creator = cursor.getStringValueOrNull(Mms.CREATOR) + val contentType = cursor.getStringValueOrNull(Mms.CONTENT_TYPE) + val deliveryReport = cursor.getIntValue(Mms.DELIVERY_REPORT) + val date = cursor.getLongValue(Mms.DATE) + val dateSent = cursor.getLongValue(Mms.DATE_SENT) + val locked = cursor.getIntValue(Mms.LOCKED) + val messageType = cursor.getIntValue(Mms.MESSAGE_TYPE) + val messageBox = cursor.getIntValue(Mms.MESSAGE_BOX) + val read = cursor.getIntValue(Mms.READ) + val readReport = cursor.getIntValue(Mms.READ_REPORT) + val seen = cursor.getIntValue(Mms.SEEN) + val textOnly = cursor.getIntValue(Mms.TEXT_ONLY) + val status = cursor.getStringValueOrNull(Mms.STATUS) + val subject = cursor.getStringValueOrNull(Mms.SUBJECT) + val subjectCharSet = cursor.getStringValueOrNull(Mms.SUBJECT_CHARSET) + val subscriptionId = cursor.getLongValue(Mms.SUBSCRIPTION_ID) + val transactionId = cursor.getStringValueOrNull(Mms.TRANSACTION_ID) + + val parts = getParts(mmsId) + val addresses = getMMSAddresses(mmsId) + block(MmsBackup(creator, contentType, deliveryReport, date, dateSent, locked, messageType, messageBox, read, readReport, seen, textOnly, status, subject, subjectCharSet, subscriptionId, transactionId, addresses, parts)) } } @SuppressLint("NewApi") - private fun getParts(mmsId: Long): JsonArray { - val jsonArray = JsonArray() + private fun getParts(mmsId: Long): List { + val parts = mutableListOf() val uri = if (isQPlus()) { - Telephony.Mms.Part.CONTENT_URI + Mms.Part.CONTENT_URI } else { Uri.parse("content://mms/part") } - - val selection = "${Telephony.Mms.Part.MSG_ID}= ?" + val projection = arrayOf( + Mms.Part._ID, + Mms.Part.CONTENT_DISPOSITION, + Mms.Part.CHARSET, + Mms.Part.CONTENT_ID, + Mms.Part.CONTENT_LOCATION, + Mms.Part.CONTENT_TYPE, + Mms.Part.CT_START, + Mms.Part.CT_TYPE, + Mms.Part.FILENAME, + Mms.Part.NAME, + Mms.Part.SEQ, + Mms.Part.TEXT + ) + val selection = "${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()) - - when { - hasTextValue -> { - part.addProperty(MMS_CONTENT, "") - } - - part.get(Telephony.Mms.Part.CONTENT_TYPE).optString?.startsWith("text/") == true -> { - part.addProperty(MMS_CONTENT, usePart(part.get(Telephony.Mms.Part._ID).asLong) { stream -> + context.queryCursor(uri, projection, selection, selectionArgs) { cursor -> + val partId = cursor.getLongValue(Mms.Part._ID) + val contentDisposition = cursor.getStringValueOrNull(Mms.Part.CONTENT_DISPOSITION) + val charset = cursor.getStringValueOrNull(Mms.Part.CHARSET) + val contentId = cursor.getStringValueOrNull(Mms.Part.CONTENT_ID) + val contentLocation = cursor.getStringValueOrNull(Mms.Part.CONTENT_LOCATION) + val contentType = cursor.getStringValue(Mms.Part.CONTENT_TYPE) + val ctStart = cursor.getStringValueOrNull(Mms.Part.CT_START) + val ctType = cursor.getStringValueOrNull(Mms.Part.CT_TYPE) + val filename = cursor.getStringValueOrNull(Mms.Part.FILENAME) + val name = cursor.getStringValueOrNull(Mms.Part.NAME) + val sequenceOrder = cursor.getIntValue(Mms.Part.SEQ) + val text = cursor.getStringValueOrNull(Mms.Part.TEXT) + val data = when { + contentType.startsWith("text/") -> { + usePart(partId) { stream -> stream.readBytes().toString(Charsets.UTF_8) - }) + } } else -> { - part.addProperty(MMS_CONTENT, usePart(part.get(Telephony.Mms.Part._ID).asLong) { stream -> + usePart(partId) { stream -> 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) - }) + } } } - jsonArray.add(part) + parts.add(MmsPart(contentDisposition, charset, contentId, contentLocation, contentType, ctStart, ctType, filename, name, sequenceOrder, text, data)) } - - return jsonArray + return parts } @SuppressLint("NewApi") private fun usePart(partId: Long, block: (InputStream) -> String): String { val partUri = if (isQPlus()) { - Telephony.Mms.Part.CONTENT_URI.buildUpon().appendPath(partId.toString()).build() + Mms.Part.CONTENT_URI.buildUpon().appendPath(partId.toString()).build() } else { Uri.parse("content://mms/part/$partId") } @@ -120,26 +195,28 @@ class MessagesReader(private val context: Context) { } @SuppressLint("NewApi") - private fun getMMSAddresses(messageId: Long): JsonArray { - val jsonArray = JsonArray() - val addressUri = if (isQPlus()) { - Telephony.Mms.Addr.getAddrUriForMessage(messageId.toString()) + private fun getMMSAddresses(messageId: Long): List { + val addresses = mutableListOf() + val uri = if (isQPlus()) { + Mms.Addr.getAddrUriForMessage(messageId.toString()) } else { Uri.parse("content://mms/$messageId/addr") } - - val projection = arrayOf(Telephony.Mms.Addr.ADDRESS, Telephony.Mms.Addr.TYPE) - val selection = "${Telephony.Mms.Addr.MSG_ID}= ?" + val projection = arrayOf(Mms.Addr.ADDRESS, Mms.Addr.TYPE, Mms.Addr.CHARSET) + val selection = "${Mms.Addr.MSG_ID}= ?" val selectionArgs = arrayOf(messageId.toString()) - 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)) -// } + context.queryCursor(uri, projection, selection, selectionArgs) { cursor -> + val address = cursor.getStringValue(Mms.Addr.ADDRESS) + val type = cursor.getIntValue(Mms.Addr.TYPE) + val charset = cursor.getIntValue(Mms.Addr.CHARSET) + addresses.add(MmsAddress(address, type, charset)) } - return jsonArray + return addresses + } + + fun getMessagesCount(): Long { + return 0 } } 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 0c17db15..10c9d5a9 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesWriter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesWriter.kt @@ -5,20 +5,19 @@ import android.content.ContentValues import android.content.Context import android.net.Uri import android.provider.Telephony -import android.provider.Telephony.Mms +import android.provider.Telephony.* import android.util.Base64 import android.util.Log +import androidx.core.content.contentValuesOf 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.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 { @@ -31,66 +30,79 @@ class MessagesWriter(private val context: Context) { fun writeSmsMessage(smsBackup: SmsBackup) { Log.w(TAG, "writeSmsMessage: smsBackup=$smsBackup") val contentValues = smsBackup.toContentValues() - replaceSmsThreadId(contentValues) + val threadId = Utils.getOrCreateThreadId(context, smsBackup.address) + contentValues.put(Sms.THREAD_ID, threadId) Log.d(TAG, "writeSmsMessage: contentValues=$contentValues") Log.d(TAG, "writeSmsMessage: type=${smsBackup.type}") - if ((smsBackup.type == Telephony.Sms.MESSAGE_TYPE_INBOX || smsBackup.type == Telephony.Sms.MESSAGE_TYPE_SENT) && !smsExist(smsBackup)) { + if (!smsExist(smsBackup)) { Log.d(TAG, "writeSmsMessage: Inserting SMS...") - val uri = Telephony.Sms.CONTENT_URI + val uri = Sms.CONTENT_URI Log.d(TAG, "writeSmsMessage: uri=$uri") - contentResolver.insert(Telephony.Sms.CONTENT_URI, contentValues) + contentResolver.insert(Sms.CONTENT_URI, contentValues) + } else { + Log.w(TAG, "SMS already exists") } + // update conversation date + updateThreadDate(threadId, smsBackup.date) } - private fun replaceSmsThreadId(contentValues: ContentValues) { - val address = contentValues.get(Telephony.Sms.ADDRESS) - val threadId = Utils.getOrCreateThreadId(context, address.toString()) - contentValues.put(Telephony.Sms.THREAD_ID, threadId) + private fun updateThreadDate( + threadId: Long, + date: Long, + ) { + Log.d(TAG, "updateThreadDate: threadId=$threadId -- date=$date") + val selection = "${Threads._ID} = ?" + val selectionArgs = arrayOf(threadId.toString()) + val threadValues = contentValuesOf(Threads.DATE to date) + Log.d(TAG, "threadValues=$threadValues") + val result = contentResolver.update(Threads.CONTENT_URI.buildUpon().appendPath(threadId.toString()).build(), threadValues, null, null) + Log.d(TAG, "updateThreadDate: id=$result") } 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 uri = Sms.CONTENT_URI + val projection = arrayOf(Sms._ID) + val selection = "${Sms.DATE} = ? AND ${Sms.ADDRESS} = ? AND ${Sms.TYPE} = ?" val selectionArgs = arrayOf(smsBackup.date.toString(), smsBackup.address, smsBackup.type.toString()) - var exists = false context.queryCursor(uri, projection, selection, selectionArgs) { exists = it.count > 0 Log.i(TAG, "smsExist After: $exists") } Log.i(TAG, "smsExist: $exists") - return exists } 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 + // 2. write parts - parts depend on the msg id, check if part exist before writing, write data if it is a non-text part // 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) + contentValues.put(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) + if (!mmsExist(mmsBackup)) { + contentResolver.insert(Mms.CONTENT_URI, contentValues) + updateThreadDate(threadId, mmsBackup.date) + } else { + Log.w(TAG, "mms already exists") } - 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) } + mmsBackup.parts.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") } + } private fun getMmsThreadId(mmsBackup: MmsBackup): Long { @@ -111,14 +123,14 @@ class MessagesWriter(private val context: Context) { 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 uri = Mms.CONTENT_URI + val projection = arrayOf(Mms._ID) + val selection = "${Mms.DATE} = ? AND ${Mms.DATE_SENT} = ? AND ${Mms.THREAD_ID} = ? AND ${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) + id = it.getLongValue(Mms._ID) Log.i(TAG, "getMmsId After: $id") } Log.i(TAG, "getMmsId: $id") @@ -133,12 +145,12 @@ class MessagesWriter(private val context: Context) { @SuppressLint("NewApi") private fun mmsAddressExist(mmsAddress: MmsAddress, messageId: Long): Boolean { val addressUri = if (isQPlus()) { - Telephony.Mms.Addr.getAddrUriForMessage(messageId.toString()) + 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 projection = arrayOf(Mms.Addr._ID) + val selection = "${Mms.Addr.TYPE} = ? AND ${Mms.Addr.ADDRESS} = ? AND ${Mms.Addr.MSG_ID} = ?" val selectionArgs = arrayOf(mmsAddress.type.toString(), mmsAddress.address.toString(), messageId.toString()) var exists = false @@ -155,13 +167,13 @@ class MessagesWriter(private val context: Context) { private fun writeMmsAddress(mmsAddress: MmsAddress, messageId: Long) { if (!mmsAddressExist(mmsAddress, messageId)) { val addressUri = if (isQPlus()) { - Telephony.Mms.Addr.getAddrUriForMessage(messageId.toString()) + Mms.Addr.getAddrUriForMessage(messageId.toString()) } else { Uri.parse("content://mms/$messageId/addr") } val contentValues = mmsAddress.toContentValues() - contentValues.put(Telephony.Mms.Addr.MSG_ID, messageId) + contentValues.put(Mms.Addr.MSG_ID, messageId) contentResolver.insert(addressUri, contentValues) } else { Log.w(TAG, "writeMmsAddress: Skip already exists") @@ -182,14 +194,13 @@ class MessagesWriter(private val context: Context) { if (partUri != null) { if (mmsPart.isNonText()) { contentResolver.openOutputStream(partUri).use { - val arr = Base64.decode(mmsPart.mmsContent, Base64.DEFAULT) + val arr = Base64.decode(mmsPart.data, 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") } @@ -202,9 +213,9 @@ class MessagesWriter(private val context: Context) { @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 projection = arrayOf(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} = ?" + "${Mms.Part.CONTENT_LOCATION} = ? AND ${Mms.Part.CT_TYPE} = ? AND ${Mms.Part.MSG_ID} = ? AND ${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) { @@ -220,6 +231,6 @@ class MessagesWriter(private val context: Context) { // 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 - contentResolver.delete(Telephony.Sms.Conversations.CONTENT_URI.buildUpon().appendPath("-1").build(), null, null) + contentResolver.delete(Sms.Conversations.CONTENT_URI.buildUpon().appendPath("-1").build(), null, null) } } 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 7239d813..35a5fddc 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ExportedMessage.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/ExportedMessage.kt @@ -3,8 +3,6 @@ package com.simplemobiletools.smsmessenger.models import com.google.gson.annotations.SerializedName data class ExportedMessage( - @SerializedName("threadId") - val threadId: Long, @SerializedName("sms") val sms: List, @SerializedName("mms") diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsAddress.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsAddress.kt index 687dabf3..07148905 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsAddress.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsAddress.kt @@ -8,8 +8,6 @@ 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") diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsBackup.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsBackup.kt index e1ed2aac..e8957d93 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsBackup.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsBackup.kt @@ -18,8 +18,6 @@ data class MmsBackup( val dateSent: Long, @SerializedName("locked") val locked: Int, - @SerializedName("m_id") - val messageId: String?, @SerializedName("m_type") val messageType: Int, @SerializedName("msg_box") @@ -40,14 +38,12 @@ data class MmsBackup( 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, + val parts: List, ) { fun toContentValues(): ContentValues { diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsPart.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsPart.kt index b63f4eaf..ac5d53d1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsPart.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/MmsPart.kt @@ -20,22 +20,16 @@ data class MmsPart( 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, + val name: String?, @SerializedName("seq") val sequenceOrder: Int, @SerializedName("text") val text: String?, - @SerializedName("mms_content") - val mmsContent: String?, + @SerializedName("data") + val data: String?, ) { fun toContentValues(): ContentValues { 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 da289a2a..a6daa883 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/SmsBackup.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/models/SmsBackup.kt @@ -7,14 +7,12 @@ import androidx.core.content.contentValuesOf import com.google.gson.annotations.SerializedName data class SmsBackup( - @SerializedName("thread_id") - val threadId: Long, @SerializedName("sub_id") val subscriptionId: Long, @SerializedName("address") val address: String, @SerializedName("body") - val body: String, + val body: String?, @SerializedName("date") val date: Long, @SerializedName("date_sent") @@ -35,7 +33,6 @@ data class SmsBackup( 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,