diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt index 310c81f9..47aac9c5 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/activities/SettingsActivity.kt @@ -28,6 +28,7 @@ import kotlin.system.exitProcess class SettingsActivity : SimpleActivity() { private var blockedNumbersAtPause = -1 private val messagesFileType = "application/json" + private val messageImportFileType = "application/json, application/xml, text/plain" override fun onCreate(savedInstanceState: Bundle?) { isMaterialActivity = true @@ -83,7 +84,13 @@ class SettingsActivity : SimpleActivity() { private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> if (uri != null) { toast(R.string.importing) - importMessages(uri) + MessagesImporter(this).importMessages(uri) { deserializedList -> + if (deserializedList.isEmpty()) { + toast(R.string.no_entries_for_importing) + } else { + ImportMessagesDialog(this, deserializedList) + } + } } } @@ -104,7 +111,7 @@ class SettingsActivity : SimpleActivity() { private fun setupMessagesImport() { settings_import_messages_holder.setOnClickListener { - getContent.launch(messagesFileType) + getContent.launch(messageImportFileType) } } @@ -131,27 +138,6 @@ class SettingsActivity : SimpleActivity() { } } - private fun importMessages(uri: Uri) { - try { - val jsonString = contentResolver.openInputStream(uri)!!.use { inputStream -> - inputStream.bufferedReader().readText() - } - - val deserializedList = Json.decodeFromString>(jsonString) - if (deserializedList.isEmpty()) { - toast(R.string.no_entries_for_importing) - return - } - ImportMessagesDialog(this, deserializedList) - } catch (e: SerializationException) { - toast(R.string.invalid_file_format) - } catch (e: IllegalArgumentException) { - toast(R.string.invalid_file_format) - } catch (e: Exception) { - showErrorToast(e) - } - } - override fun onPause() { super.onPause() blockedNumbersAtPause = getBlockedNumbers().hashCode() diff --git a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ImportMessagesDialog.kt b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ImportMessagesDialog.kt index 24828490..626115fa 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ImportMessagesDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/dialogs/ImportMessagesDialog.kt @@ -48,7 +48,7 @@ class ImportMessagesDialog( config.importSms = view.import_sms_checkbox.isChecked config.importMms = view.import_mms_checkbox.isChecked ensureBackgroundThread { - MessagesImporter(activity).importMessages(messages) { + MessagesImporter(activity).restoreMessages(messages) { handleParseResult(it) alertDialog.dismiss() } 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 149c9489..8d04ec47 100644 --- a/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/smsmessenger/helpers/MessagesImporter.kt @@ -1,20 +1,68 @@ package com.simplemobiletools.smsmessenger.helpers -import android.content.Context +import android.net.Uri +import android.util.Xml import com.simplemobiletools.commons.extensions.showErrorToast +import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.smsmessenger.R +import com.simplemobiletools.smsmessenger.activities.SimpleActivity +import com.simplemobiletools.smsmessenger.dialogs.ImportMessagesDialog import com.simplemobiletools.smsmessenger.extensions.config import com.simplemobiletools.smsmessenger.models.* +import kotlinx.serialization.SerializationException +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import org.xmlpull.v1.XmlPullParser +import java.io.InputStream -class MessagesImporter(private val context: Context) { +class MessagesImporter(private val activity: SimpleActivity) { - private val messageWriter = MessagesWriter(context) - private val config = context.config + private val messageWriter = MessagesWriter(activity) + private val config = activity.config private var messagesImported = 0 private var messagesFailed = 0 - fun importMessages(messagesBackup: List, callback: (result: ImportResult) -> Unit) { + fun importMessages(uri: Uri) { + try { + val fileType = activity.contentResolver.getType(uri).orEmpty() + + val isXml = isXmlMimeType(fileType) || (uri.path?.endsWith("txt") == true && isFileXml(uri)) + + if (isXml) { + getInputStreamFromUri(uri)!!.importXml() + } else { + importJson(uri) + } + + } catch (e: Exception) { + activity.showErrorToast(e) + } + } + + private fun importJson(uri: Uri) { + try { + val jsonString = activity.contentResolver.openInputStream(uri)!!.use { inputStream -> + inputStream.bufferedReader().readText() + } + + val deserializedList = Json.decodeFromString>(jsonString) + if (deserializedList.isEmpty()) { + activity.toast(R.string.no_entries_for_importing) + return + } + ImportMessagesDialog(activity, deserializedList) + } catch (e: SerializationException) { + activity.toast(R.string.invalid_file_format) + } catch (e: IllegalArgumentException) { + activity.toast(R.string.invalid_file_format) + } catch (e: Exception) { + activity.showErrorToast(e) + } + } + + fun restoreMessages(messagesBackup: List, callback: (ImportResult) -> Unit) { ensureBackgroundThread { try { messagesBackup.forEach { message -> @@ -23,17 +71,16 @@ class MessagesImporter(private val context: Context) { messageWriter.writeSmsMessage(message as SmsBackup) } else if (message.backupType == BackupType.MMS && config.importMms) { messageWriter.writeMmsMessage(message as MmsBackup) - } - else return@forEach + } else return@forEach messagesImported++ } catch (e: Exception) { - context.showErrorToast(e) + activity.showErrorToast(e) messagesFailed++ } } refreshMessages() } catch (e: Exception) { - context.showErrorToast(e) + activity.showErrorToast(e) } callback.invoke( @@ -46,4 +93,96 @@ class MessagesImporter(private val context: Context) { ) } } + + 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) { + activity.showErrorToast(e) + messagesFailed++ + } + } + refreshMessages() + } + // TODO: Add result to xml import + } + + 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++ + } + } + } + + private fun getInputStreamFromUri(uri: Uri): InputStream? { + return try { + activity.contentResolver.openInputStream(uri) + } catch (e: Exception) { + null + } + } + + private fun isFileXml(uri: Uri): Boolean { + val inputStream = getInputStreamFromUri(uri) + return inputStream?.bufferedReader()?.use { reader -> + reader.readLine()?.startsWith("