diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt index 1204d748..5c6a1b5b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/MainActivity.kt @@ -582,7 +582,7 @@ class MainActivity : SimpleActivity() { if (isQPlus()) { ExportMessagesDialog(this, config.lastExportPath, true) { file -> Intent(Intent.ACTION_CREATE_DOCUMENT).apply { - type = EXPORT_MIME_TYPE + type = JSON_MIME_TYPE putExtra(Intent.EXTRA_TITLE, file.name) addCategory(Intent.CATEGORY_OPENABLE) @@ -626,7 +626,8 @@ class MainActivity : SimpleActivity() { if (isQPlus()) { Intent(Intent.ACTION_GET_CONTENT).apply { addCategory(Intent.CATEGORY_OPENABLE) - type = EXPORT_MIME_TYPE + type = JSON_MIME_TYPE + putExtra(Intent.EXTRA_MIME_TYPES, arrayOf(JSON_MIME_TYPE, XML_MIME_TYPE, TXT_MIME_TYPE)) try { startActivityForResult(this, PICK_IMPORT_SOURCE_INTENT) @@ -639,27 +640,27 @@ class MainActivity : SimpleActivity() { } else { handlePermission(PERMISSION_READ_STORAGE) { if (it) { - importEvents() + importMessages() } } } } - private fun importEvents() { + private fun importMessages() { FilePickerDialog(this) { - showImportEventsDialog(it) + showImportMessagesDialog(it) } } - private fun showImportEventsDialog(path: String) { + private fun showImportMessagesDialog(path: String) { ImportMessagesDialog(this, path) } private fun tryImportMessagesFromFile(uri: Uri) { when (uri.scheme) { - "file" -> showImportEventsDialog(uri.path!!) + "file" -> showImportMessagesDialog(uri.path!!) "content" -> { - val tempFile = getTempFile("messages", "backup.json") + var tempFile = getTempFile("messages", "backup.json") if (tempFile == null) { toast(R.string.unknown_error_occurred) return @@ -669,7 +670,18 @@ class MainActivity : SimpleActivity() { val inputStream = contentResolver.openInputStream(uri) val out = FileOutputStream(tempFile) inputStream!!.copyTo(out) - showImportEventsDialog(tempFile.absolutePath) + // Check is XML and properly rename + tempFile.bufferedReader().use { + if (it.readLine().startsWith(" activity.toast(R.string.empty_name) filename.isAValidFilename() -> { - val file = File(realPath, "$filename$EXPORT_FILE_EXT") + val file = File(realPath, "$filename$JSON_FILE_EXTENSION") if (!hidePath && file.exists()) { activity.toast(R.string.name_taken) return@setOnClickListener 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 2bbdc2bb..3bade2ac 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/Constants.kt @@ -28,8 +28,10 @@ const val PINNED_CONVERSATIONS = "pinned_conversations" const val BLOCKED_KEYWORDS = "blocked_keywords" const val EXPORT_SMS = "export_sms" const val EXPORT_MMS = "export_mms" -const val EXPORT_MIME_TYPE = "application/json" -const val EXPORT_FILE_EXT = ".json" +const val JSON_FILE_EXTENSION = ".json" +const val JSON_MIME_TYPE = "application/json" +const val XML_MIME_TYPE = "text/xml" +const val TXT_MIME_TYPE = "text/plain" const val IMPORT_SMS = "import_sms" const val IMPORT_MMS = "import_mms" const val WAS_DB_CLEARED = "was_db_cleared_2" 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 7377d057..28ac0687 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesImporter.kt @@ -2,6 +2,7 @@ package com.simplemobiletools.smsmessenger.helpers import android.content.Context import android.util.JsonToken +import android.util.Xml import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.simplemobiletools.commons.extensions.showErrorToast @@ -13,7 +14,9 @@ import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult. import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_PARTIAL import com.simplemobiletools.smsmessenger.models.MmsBackup import com.simplemobiletools.smsmessenger.models.SmsBackup +import org.xmlpull.v1.XmlPullParser import java.io.File +import java.io.InputStream class MessagesImporter(private val context: Context) { enum class ImportResult { @@ -29,63 +32,24 @@ class MessagesImporter(private val context: Context) { fun importMessages(path: String, onProgress: (total: Int, current: Int) -> Unit = { _, _ -> }, callback: (result: ImportResult) -> Unit) { ensureBackgroundThread { try { - val inputStream = if (path.contains("/")) { - File(path).inputStream() - } else { - context.assets.open(path) - } - - inputStream.bufferedReader().use { reader -> - val jsonReader = gson.newJsonReader(reader) - val smsMessageType = object : TypeToken() {}.type - val mmsMessageType = object : TypeToken() {}.type - - jsonReader.beginArray() - while (jsonReader.hasNext()) { - jsonReader.beginObject() - while (jsonReader.hasNext()) { - val nextToken = jsonReader.peek() - if (nextToken.ordinal == JsonToken.NAME.ordinal) { - val msgType = jsonReader.nextName() - - if ((!msgType.equals("sms") && !msgType.equals("mms")) || - (msgType.equals("sms") && !config.importSms) || - (msgType.equals("mms") && !config.importMms) - ) { - jsonReader.skipValue() - continue - } - - jsonReader.beginArray() - while (jsonReader.hasNext()) { - try { - if (msgType.equals("sms")) { - val message = gson.fromJson(jsonReader, smsMessageType) - messageWriter.writeSmsMessage(message) - } else { - val message = gson.fromJson(jsonReader, mmsMessageType) - messageWriter.writeMmsMessage(message) - } - - messagesImported++ - } catch (e: Exception) { - context.showErrorToast(e) - messagesFailed++ - - } - } - jsonReader.endArray() - } else { - jsonReader.skipValue() - } - } - - jsonReader.endObject() - refreshMessages() + val isXml = if (path.endsWith("txt")) { + // Need to read the first line to determine if it is xml + val tempStream = getInputStreamForPath(path) + tempStream.bufferedReader().use { + it.readLine().startsWith(" + val jsonReader = gson.newJsonReader(reader) + val smsMessageType = object : TypeToken() {}.type + val mmsMessageType = object : TypeToken() {}.type + + jsonReader.beginArray() + while (jsonReader.hasNext()) { + jsonReader.beginObject() + while (jsonReader.hasNext()) { + val nextToken = jsonReader.peek() + if (nextToken.ordinal == JsonToken.NAME.ordinal) { + val msgType = jsonReader.nextName() + + if ((!msgType.equals("sms") && !msgType.equals("mms")) || + (msgType.equals("sms") && !config.importSms) || + (msgType.equals("mms") && !config.importMms) + ) { + jsonReader.skipValue() + continue + } + + jsonReader.beginArray() + while (jsonReader.hasNext()) { + try { + if (msgType.equals("sms")) { + val message = gson.fromJson(jsonReader, smsMessageType) + messageWriter.writeSmsMessage(message) + } else { + val message = gson.fromJson(jsonReader, mmsMessageType) + messageWriter.writeMmsMessage(message) + } + + messagesImported++ + } catch (e: Exception) { + context.showErrorToast(e) + messagesFailed++ + + } + } + jsonReader.endArray() + } else { + jsonReader.skipValue() + } + } + + jsonReader.endObject() + refreshMessages() + } + + jsonReader.endArray() + } + } + + private fun InputStream.importXml() { + bufferedReader().use { reader -> + val xmlParser = Xml.newPullParser().apply { + setInput(reader) + } + + xmlParser.nextTag() + xmlParser.require(XmlPullParser.START_TAG, null, "smses") + + var depth = 1 + while (depth != 0) { + when (xmlParser.next()) { + XmlPullParser.END_TAG -> depth-- + XmlPullParser.START_TAG -> depth++ + } + + if (xmlParser.eventType != XmlPullParser.START_TAG) { + continue + } + + try { + if (xmlParser.name == "sms") { + if (config.importSms) { + val message = xmlParser.readSms() + messageWriter.writeSmsMessage(message) + messagesImported++ + } else { + xmlParser.skip() + } + } else { + xmlParser.skip() + } + } catch (e: Exception) { + context.showErrorToast(e) + messagesFailed++ + } + } + + refreshMessages() + } + } + + private fun XmlPullParser.readSms(): SmsBackup { + require(XmlPullParser.START_TAG, null, "sms") + + return SmsBackup( + subscriptionId = 0, + address = getAttributeValue(null, "address"), + body = getAttributeValue(null, "body"), + date = getAttributeValue(null, "date").toLong(), + dateSent = getAttributeValue(null, "date").toLong(), + locked = getAttributeValue(null, "locked").toInt(), + protocol = getAttributeValue(null, "protocol"), + read = getAttributeValue(null, "read").toInt(), + status = getAttributeValue(null, "status").toInt(), + type = getAttributeValue(null, "type").toInt(), + serviceCenter = getAttributeValue(null, "service_center") + ) + } + + private fun XmlPullParser.skip() { + if (eventType != XmlPullParser.START_TAG) { + throw IllegalStateException() + } + var depth = 1 + while (depth != 0) { + when (next()) { + XmlPullParser.END_TAG -> depth-- + XmlPullParser.START_TAG -> depth++ + } + } + } }