mirror of
https://github.com/SimpleMobileTools/Simple-SMS-Messenger.git
synced 2025-06-05 21:49:22 +02:00
Add support for importing XML exports
This covers importing from Signal and Silence applications export This closes #519
This commit is contained in:
@@ -627,6 +627,7 @@ class MainActivity : SimpleActivity() {
|
|||||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
type = EXPORT_MIME_TYPE
|
type = EXPORT_MIME_TYPE
|
||||||
|
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf(EXPORT_MIME_TYPE, XML_IMPORT_MIME_TYPE))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
startActivityForResult(this, PICK_IMPORT_SOURCE_INTENT)
|
startActivityForResult(this, PICK_IMPORT_SOURCE_INTENT)
|
||||||
@@ -639,27 +640,27 @@ class MainActivity : SimpleActivity() {
|
|||||||
} else {
|
} else {
|
||||||
handlePermission(PERMISSION_READ_STORAGE) {
|
handlePermission(PERMISSION_READ_STORAGE) {
|
||||||
if (it) {
|
if (it) {
|
||||||
importEvents()
|
importMessages()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun importEvents() {
|
private fun importMessages() {
|
||||||
FilePickerDialog(this) {
|
FilePickerDialog(this) {
|
||||||
showImportEventsDialog(it)
|
showImportMessagesDialog(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showImportEventsDialog(path: String) {
|
private fun showImportMessagesDialog(path: String) {
|
||||||
ImportMessagesDialog(this, path)
|
ImportMessagesDialog(this, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryImportMessagesFromFile(uri: Uri) {
|
private fun tryImportMessagesFromFile(uri: Uri) {
|
||||||
when (uri.scheme) {
|
when (uri.scheme) {
|
||||||
"file" -> showImportEventsDialog(uri.path!!)
|
"file" -> showImportMessagesDialog(uri.path!!)
|
||||||
"content" -> {
|
"content" -> {
|
||||||
val tempFile = getTempFile("messages", "backup.json")
|
var tempFile = getTempFile("messages", "backup.json")
|
||||||
if (tempFile == null) {
|
if (tempFile == null) {
|
||||||
toast(R.string.unknown_error_occurred)
|
toast(R.string.unknown_error_occurred)
|
||||||
return
|
return
|
||||||
@@ -669,7 +670,18 @@ class MainActivity : SimpleActivity() {
|
|||||||
val inputStream = contentResolver.openInputStream(uri)
|
val inputStream = contentResolver.openInputStream(uri)
|
||||||
val out = FileOutputStream(tempFile)
|
val out = FileOutputStream(tempFile)
|
||||||
inputStream!!.copyTo(out)
|
inputStream!!.copyTo(out)
|
||||||
showImportEventsDialog(tempFile.absolutePath)
|
// Check is XML and properly rename
|
||||||
|
tempFile.bufferedReader().use {
|
||||||
|
if (it.readLine().startsWith("<?xml")) {
|
||||||
|
val xmlFile = getTempFile("messages", "backup.xml")
|
||||||
|
if (xmlFile == null || tempFile?.renameTo(xmlFile) == false) {
|
||||||
|
toast(R.string.unknown_error_occurred)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tempFile = xmlFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showImportMessagesDialog(tempFile!!.absolutePath)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
showErrorToast(e)
|
showErrorToast(e)
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,7 @@ const val EXPORT_SMS = "export_sms"
|
|||||||
const val EXPORT_MMS = "export_mms"
|
const val EXPORT_MMS = "export_mms"
|
||||||
const val EXPORT_MIME_TYPE = "application/json"
|
const val EXPORT_MIME_TYPE = "application/json"
|
||||||
const val EXPORT_FILE_EXT = ".json"
|
const val EXPORT_FILE_EXT = ".json"
|
||||||
|
const val XML_IMPORT_MIME_TYPE = "text/xml"
|
||||||
const val IMPORT_SMS = "import_sms"
|
const val IMPORT_SMS = "import_sms"
|
||||||
const val IMPORT_MMS = "import_mms"
|
const val IMPORT_MMS = "import_mms"
|
||||||
const val WAS_DB_CLEARED = "was_db_cleared_2"
|
const val WAS_DB_CLEARED = "was_db_cleared_2"
|
||||||
|
@@ -2,6 +2,7 @@ package com.simplemobiletools.smsmessenger.helpers
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.JsonToken
|
import android.util.JsonToken
|
||||||
|
import android.util.Xml
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.simplemobiletools.commons.extensions.showErrorToast
|
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.helpers.MessagesImporter.ImportResult.IMPORT_PARTIAL
|
||||||
import com.simplemobiletools.smsmessenger.models.MmsBackup
|
import com.simplemobiletools.smsmessenger.models.MmsBackup
|
||||||
import com.simplemobiletools.smsmessenger.models.SmsBackup
|
import com.simplemobiletools.smsmessenger.models.SmsBackup
|
||||||
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
class MessagesImporter(private val context: Context) {
|
class MessagesImporter(private val context: Context) {
|
||||||
enum class ImportResult {
|
enum class ImportResult {
|
||||||
@@ -35,57 +38,12 @@ class MessagesImporter(private val context: Context) {
|
|||||||
context.assets.open(path)
|
context.assets.open(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
inputStream.bufferedReader().use { reader ->
|
if (path.endsWith("xml")) {
|
||||||
val jsonReader = gson.newJsonReader(reader)
|
inputStream.importXml()
|
||||||
val smsMessageType = object : TypeToken<SmsBackup>() {}.type
|
} else {
|
||||||
val mmsMessageType = object : TypeToken<MmsBackup>() {}.type
|
inputStream.importJson()
|
||||||
|
|
||||||
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<SmsBackup>(jsonReader, smsMessageType)
|
|
||||||
messageWriter.writeSmsMessage(message)
|
|
||||||
} else {
|
|
||||||
val message = gson.fromJson<MmsBackup>(jsonReader, mmsMessageType)
|
|
||||||
messageWriter.writeMmsMessage(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
messagesImported++
|
|
||||||
} catch (e: Exception) {
|
|
||||||
context.showErrorToast(e)
|
|
||||||
messagesFailed++
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jsonReader.endArray()
|
|
||||||
} else {
|
|
||||||
jsonReader.skipValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonReader.endObject()
|
|
||||||
refreshMessages()
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonReader.endArray()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
context.showErrorToast(e)
|
context.showErrorToast(e)
|
||||||
messagesFailed++
|
messagesFailed++
|
||||||
@@ -101,4 +59,131 @@ class MessagesImporter(private val context: Context) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun InputStream.importJson() {
|
||||||
|
bufferedReader().use { reader ->
|
||||||
|
val jsonReader = gson.newJsonReader(reader)
|
||||||
|
val smsMessageType = object : TypeToken<SmsBackup>() {}.type
|
||||||
|
val mmsMessageType = object : TypeToken<MmsBackup>() {}.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<SmsBackup>(jsonReader, smsMessageType)
|
||||||
|
messageWriter.writeSmsMessage(message)
|
||||||
|
} else {
|
||||||
|
val message = gson.fromJson<MmsBackup>(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++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user