mirror of
https://github.com/SimpleMobileTools/Simple-SMS-Messenger.git
synced 2025-06-05 21:49:22 +02:00
Merge pull request #699 from Merkost/export_import_settings
Export import settings section
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-android-extensions'
|
||||
id 'kotlin-kapt'
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version"
|
||||
}
|
||||
|
||||
def keystorePropertiesFile = rootProject.file("keystore.properties")
|
||||
def keystoreProperties = new Properties()
|
||||
@@ -71,6 +74,7 @@ dependencies {
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'com.googlecode.ez-vcard:ez-vcard:0.11.3'
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.5.1'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
|
||||
|
||||
kapt "androidx.room:room-compiler:2.5.2"
|
||||
implementation "androidx.room:room-runtime:2.5.2"
|
||||
|
24
app/proguard-rules.pro
vendored
24
app/proguard-rules.pro
vendored
@@ -4,3 +4,27 @@
|
||||
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||
}
|
||||
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||
|
||||
# Keep `Companion` object fields of serializable classes.
|
||||
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
|
||||
-if @kotlinx.serialization.Serializable class **
|
||||
-keepclassmembers class <1> {
|
||||
static <1>$Companion Companion;
|
||||
}
|
||||
|
||||
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
|
||||
-if @kotlinx.serialization.Serializable class ** {
|
||||
static **$* *;
|
||||
}
|
||||
-keepclassmembers class <2>$<3> {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
# Keep `INSTANCE.serializer()` of serializable objects.
|
||||
-if @kotlinx.serialization.Serializable class ** {
|
||||
public static ** INSTANCE;
|
||||
}
|
||||
-keepclassmembers class <1> {
|
||||
public static <1> INSTANCE;
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
@@ -3,19 +3,15 @@ package com.simplemobiletools.smsmessenger.activities
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.role.RoleManager
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.graphics.drawable.Icon
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.Telephony
|
||||
import android.text.TextUtils
|
||||
import android.widget.Toast
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import com.simplemobiletools.commons.dialogs.FilePickerDialog
|
||||
import com.simplemobiletools.commons.dialogs.PermissionRequiredDialog
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.*
|
||||
@@ -25,8 +21,6 @@ import com.simplemobiletools.smsmessenger.BuildConfig
|
||||
import com.simplemobiletools.smsmessenger.R
|
||||
import com.simplemobiletools.smsmessenger.adapters.ConversationsAdapter
|
||||
import com.simplemobiletools.smsmessenger.adapters.SearchResultsAdapter
|
||||
import com.simplemobiletools.smsmessenger.dialogs.ExportMessagesDialog
|
||||
import com.simplemobiletools.smsmessenger.dialogs.ImportMessagesDialog
|
||||
import com.simplemobiletools.smsmessenger.extensions.*
|
||||
import com.simplemobiletools.smsmessenger.helpers.*
|
||||
import com.simplemobiletools.smsmessenger.models.Conversation
|
||||
@@ -37,19 +31,14 @@ import kotlinx.android.synthetic.main.activity_main.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.io.FileOutputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
class MainActivity : SimpleActivity() {
|
||||
private val MAKE_DEFAULT_APP_REQUEST = 1
|
||||
private val PICK_IMPORT_SOURCE_INTENT = 11
|
||||
private val PICK_EXPORT_FILE_INTENT = 21
|
||||
|
||||
private var storedTextColor = 0
|
||||
private var storedFontSize = 0
|
||||
private var lastSearchedText = ""
|
||||
private var bus: EventBus? = null
|
||||
private val smsExporter by lazy { MessagesExporter(this) }
|
||||
private var wasProtectionHandled = false
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
@@ -174,8 +163,6 @@ class MainActivity : SimpleActivity() {
|
||||
|
||||
main_menu.getToolbar().setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.import_messages -> tryImportMessages()
|
||||
R.id.export_messages -> tryToExportMessages()
|
||||
R.id.more_apps_from_us -> launchMoreAppsFromUsIntent()
|
||||
R.id.show_archived -> launchArchivedConversations()
|
||||
R.id.settings -> launchSettings()
|
||||
@@ -200,11 +187,6 @@ class MainActivity : SimpleActivity() {
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
} else if (requestCode == PICK_IMPORT_SOURCE_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
|
||||
tryImportMessagesFromFile(resultData.data!!)
|
||||
} else if (requestCode == PICK_EXPORT_FILE_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
|
||||
val outputStream = contentResolver.openOutputStream(resultData.data!!)
|
||||
exportMessagesTo(outputStream)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,119 +575,6 @@ class MainActivity : SimpleActivity() {
|
||||
startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true)
|
||||
}
|
||||
|
||||
private fun tryToExportMessages() {
|
||||
if (isQPlus()) {
|
||||
ExportMessagesDialog(this, config.lastExportPath, true) { file ->
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
type = JSON_MIME_TYPE
|
||||
putExtra(Intent.EXTRA_TITLE, file.name)
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
|
||||
try {
|
||||
startActivityForResult(this, PICK_EXPORT_FILE_INTENT)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
toast(R.string.system_service_disabled, Toast.LENGTH_LONG)
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handlePermission(PERMISSION_WRITE_STORAGE) {
|
||||
if (it) {
|
||||
ExportMessagesDialog(this, config.lastExportPath, false) { file ->
|
||||
getFileOutputStream(file.toFileDirItem(this), true) { outStream ->
|
||||
exportMessagesTo(outStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportMessagesTo(outputStream: OutputStream?) {
|
||||
toast(R.string.exporting)
|
||||
ensureBackgroundThread {
|
||||
smsExporter.exportMessages(outputStream) {
|
||||
val toastId = when (it) {
|
||||
MessagesExporter.ExportResult.EXPORT_OK -> R.string.exporting_successful
|
||||
else -> R.string.exporting_failed
|
||||
}
|
||||
|
||||
toast(toastId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryImportMessages() {
|
||||
if (isQPlus()) {
|
||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
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)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
toast(R.string.system_service_disabled, Toast.LENGTH_LONG)
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handlePermission(PERMISSION_READ_STORAGE) {
|
||||
if (it) {
|
||||
importMessages()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun importMessages() {
|
||||
FilePickerDialog(this) {
|
||||
showImportMessagesDialog(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showImportMessagesDialog(path: String) {
|
||||
ImportMessagesDialog(this, path)
|
||||
}
|
||||
|
||||
private fun tryImportMessagesFromFile(uri: Uri) {
|
||||
when (uri.scheme) {
|
||||
"file" -> showImportMessagesDialog(uri.path!!)
|
||||
"content" -> {
|
||||
var tempFile = getTempFile("messages", "backup.json")
|
||||
if (tempFile == null) {
|
||||
toast(R.string.unknown_error_occurred)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val inputStream = contentResolver.openInputStream(uri)
|
||||
val out = FileOutputStream(tempFile)
|
||||
inputStream!!.copyTo(out)
|
||||
// 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) {
|
||||
showErrorToast(e)
|
||||
}
|
||||
}
|
||||
|
||||
else -> toast(R.string.invalid_file_format)
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun refreshMessages(event: Events.RefreshMessages) {
|
||||
initMessenger()
|
||||
|
@@ -2,21 +2,30 @@ package com.simplemobiletools.smsmessenger.activities
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.simplemobiletools.commons.activities.ManageBlockedNumbersActivity
|
||||
import com.simplemobiletools.commons.dialogs.*
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.*
|
||||
import com.simplemobiletools.commons.models.RadioItem
|
||||
import com.simplemobiletools.smsmessenger.R
|
||||
import com.simplemobiletools.smsmessenger.dialogs.ExportMessagesDialog
|
||||
import com.simplemobiletools.smsmessenger.extensions.config
|
||||
import com.simplemobiletools.smsmessenger.helpers.*
|
||||
import com.simplemobiletools.smsmessenger.models.*
|
||||
import kotlinx.android.synthetic.main.activity_settings.*
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.*
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class SettingsActivity : SimpleActivity() {
|
||||
private var blockedNumbersAtPause = -1
|
||||
private val messagesFileType = "application/json"
|
||||
private val messageImportFileTypes = listOf("application/json", "application/xml", "text/xml")
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
isMaterialActivity = true
|
||||
@@ -49,6 +58,8 @@ class SettingsActivity : SimpleActivity() {
|
||||
setupLockScreenVisibility()
|
||||
setupMMSFileSizeLimit()
|
||||
setupAppPasswordProtection()
|
||||
setupMessagesExport()
|
||||
setupMessagesImport()
|
||||
updateTextColors(settings_nested_scrollview)
|
||||
|
||||
if (blockedNumbersAtPause != -1 && blockedNumbersAtPause != getBlockedNumbers().hashCode()) {
|
||||
@@ -60,12 +71,63 @@ class SettingsActivity : SimpleActivity() {
|
||||
settings_general_settings_label,
|
||||
settings_outgoing_messages_label,
|
||||
settings_notifications_label,
|
||||
settings_security_label
|
||||
settings_security_label,
|
||||
settings_migrating_label
|
||||
).forEach {
|
||||
it.setTextColor(getProperPrimaryColor())
|
||||
}
|
||||
}
|
||||
|
||||
private val getContent = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
||||
if (uri != null) {
|
||||
MessagesImporter(this).importMessages(uri)
|
||||
}
|
||||
}
|
||||
|
||||
private val saveDocument = registerForActivityResult(ActivityResultContracts.CreateDocument(messagesFileType)) { uri ->
|
||||
if (uri != null) {
|
||||
toast(R.string.exporting)
|
||||
exportMessages(uri)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMessagesExport() {
|
||||
settings_export_messages_holder.setOnClickListener {
|
||||
ExportMessagesDialog(this) { fileName ->
|
||||
saveDocument.launch(fileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMessagesImport() {
|
||||
settings_import_messages_holder.setOnClickListener {
|
||||
getContent.launch(messageImportFileTypes.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportMessages(uri: Uri) {
|
||||
ensureBackgroundThread {
|
||||
try {
|
||||
MessagesReader(this).getMessagesToExport(config.exportSms, config.exportMms) { messagesToExport ->
|
||||
if (messagesToExport.isEmpty()) {
|
||||
toast(R.string.no_entries_for_exporting)
|
||||
return@getMessagesToExport
|
||||
}
|
||||
val json = Json { encodeDefaults = true }
|
||||
val jsonString = json.encodeToString(messagesToExport)
|
||||
val outputStream = contentResolver.openOutputStream(uri)!!
|
||||
|
||||
outputStream.use {
|
||||
it.write(jsonString.toByteArray())
|
||||
}
|
||||
toast(R.string.exporting_successful)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
blockedNumbersAtPause = getBlockedNumbers().hashCode()
|
||||
@@ -98,7 +160,7 @@ class SettingsActivity : SimpleActivity() {
|
||||
settings_use_english_holder.setOnClickListener {
|
||||
settings_use_english.toggle()
|
||||
config.useEnglish = settings_use_english.isChecked
|
||||
System.exit(0)
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,42 +2,27 @@ package com.simplemobiletools.smsmessenger.dialogs
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.simplemobiletools.commons.dialogs.FilePickerDialog
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.smsmessenger.R
|
||||
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
|
||||
import com.simplemobiletools.smsmessenger.extensions.config
|
||||
import com.simplemobiletools.smsmessenger.helpers.JSON_FILE_EXTENSION
|
||||
import kotlinx.android.synthetic.main.dialog_export_messages.view.*
|
||||
import java.io.File
|
||||
import kotlinx.android.synthetic.main.dialog_export_messages.view.export_messages_filename
|
||||
import kotlinx.android.synthetic.main.dialog_export_messages.view.export_mms_checkbox
|
||||
import kotlinx.android.synthetic.main.dialog_export_messages.view.export_sms_checkbox
|
||||
|
||||
class ExportMessagesDialog(
|
||||
private val activity: SimpleActivity,
|
||||
private val path: String,
|
||||
private val hidePath: Boolean,
|
||||
private val callback: (file: File) -> Unit,
|
||||
private val callback: (fileName: String) -> Unit,
|
||||
) {
|
||||
private var realPath = if (path.isEmpty()) activity.internalStoragePath else path
|
||||
private val config = activity.config
|
||||
|
||||
init {
|
||||
val view = (activity.layoutInflater.inflate(R.layout.dialog_export_messages, null) as ViewGroup).apply {
|
||||
export_messages_folder.setText(activity.humanizePath(realPath))
|
||||
export_messages_filename.setText("${activity.getString(R.string.messages)}_${activity.getCurrentFormattedDateTime()}")
|
||||
export_sms_checkbox.isChecked = config.exportSms
|
||||
export_mms_checkbox.isChecked = config.exportMms
|
||||
|
||||
if (hidePath) {
|
||||
export_messages_folder_hint.beGone()
|
||||
} else {
|
||||
export_messages_folder.setOnClickListener {
|
||||
activity.hideKeyboard(export_messages_filename)
|
||||
FilePickerDialog(activity, realPath, false, showFAB = true) {
|
||||
export_messages_folder.setText(activity.humanizePath(it))
|
||||
realPath = it
|
||||
}
|
||||
}
|
||||
}
|
||||
export_messages_filename.setText(
|
||||
activity.getString(R.string.messages) + "_" + activity.getCurrentFormattedDateTime()
|
||||
)
|
||||
}
|
||||
|
||||
activity.getAlertDialogBuilder()
|
||||
@@ -45,29 +30,17 @@ class ExportMessagesDialog(
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.apply {
|
||||
activity.setupDialogStuff(view, this, R.string.export_messages) { alertDialog ->
|
||||
alertDialog.showKeyboard(view.export_messages_filename)
|
||||
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||
config.exportSms = view.export_sms_checkbox.isChecked
|
||||
config.exportMms = view.export_mms_checkbox.isChecked
|
||||
val filename = view.export_messages_filename.value
|
||||
when {
|
||||
filename.isEmpty() -> activity.toast(R.string.empty_name)
|
||||
filename.isAValidFilename() -> {
|
||||
val file = File(realPath, "$filename$JSON_FILE_EXTENSION")
|
||||
if (!hidePath && file.exists()) {
|
||||
activity.toast(R.string.name_taken)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
if (!view.export_sms_checkbox.isChecked && !view.export_mms_checkbox.isChecked) {
|
||||
activity.toast(R.string.no_option_selected)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
config.exportSms = view.export_sms_checkbox.isChecked
|
||||
config.exportMms = view.export_mms_checkbox.isChecked
|
||||
config.lastExportPath = file.absolutePath.getParentPath()
|
||||
callback(file)
|
||||
callback(filename)
|
||||
alertDialog.dismiss()
|
||||
}
|
||||
|
||||
else -> activity.toast(R.string.invalid_name)
|
||||
}
|
||||
}
|
||||
|
@@ -10,13 +10,13 @@ import com.simplemobiletools.smsmessenger.R
|
||||
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
|
||||
import com.simplemobiletools.smsmessenger.extensions.config
|
||||
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter
|
||||
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_OK
|
||||
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_PARTIAL
|
||||
import com.simplemobiletools.smsmessenger.models.MessagesBackup
|
||||
import com.simplemobiletools.smsmessenger.models.ImportResult
|
||||
import kotlinx.android.synthetic.main.dialog_import_messages.view.*
|
||||
|
||||
class ImportMessagesDialog(
|
||||
private val activity: SimpleActivity,
|
||||
private val path: String,
|
||||
private val messages: List<MessagesBackup>,
|
||||
) {
|
||||
|
||||
private val config = activity.config
|
||||
@@ -48,7 +48,7 @@ class ImportMessagesDialog(
|
||||
config.importSms = view.import_sms_checkbox.isChecked
|
||||
config.importMms = view.import_mms_checkbox.isChecked
|
||||
ensureBackgroundThread {
|
||||
MessagesImporter(activity).importMessages(path) {
|
||||
MessagesImporter(activity).restoreMessages(messages) {
|
||||
handleParseResult(it)
|
||||
alertDialog.dismiss()
|
||||
}
|
||||
@@ -58,11 +58,12 @@ class ImportMessagesDialog(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleParseResult(result: MessagesImporter.ImportResult) {
|
||||
private fun handleParseResult(result: ImportResult) {
|
||||
activity.toast(
|
||||
when (result) {
|
||||
IMPORT_OK -> R.string.importing_successful
|
||||
IMPORT_PARTIAL -> R.string.importing_some_entries_failed
|
||||
ImportResult.IMPORT_OK -> R.string.importing_successful
|
||||
ImportResult.IMPORT_PARTIAL -> R.string.importing_some_entries_failed
|
||||
ImportResult.IMPORT_FAIL -> R.string.importing_failed
|
||||
else -> R.string.no_items_found
|
||||
}
|
||||
)
|
||||
|
@@ -1,68 +0,0 @@
|
||||
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
|
||||
import com.simplemobiletools.smsmessenger.extensions.getConversationIds
|
||||
import java.io.OutputStream
|
||||
|
||||
class MessagesExporter(private val context: Context) {
|
||||
enum class ExportResult {
|
||||
EXPORT_FAIL, EXPORT_OK
|
||||
}
|
||||
|
||||
private val config = context.config
|
||||
private val messageReader = MessagesReader(context)
|
||||
private val gson = Gson()
|
||||
|
||||
fun exportMessages(outputStream: OutputStream?, onProgress: (total: Int, current: Int) -> Unit = { _, _ -> }, callback: (result: ExportResult) -> Unit) {
|
||||
ensureBackgroundThread {
|
||||
if (outputStream == null) {
|
||||
callback.invoke(ExportResult.EXPORT_FAIL)
|
||||
return@ensureBackgroundThread
|
||||
}
|
||||
val writer = JsonWriter(outputStream.bufferedWriter())
|
||||
writer.use {
|
||||
try {
|
||||
var written = 0
|
||||
writer.beginArray()
|
||||
val conversationIds = context.getConversationIds()
|
||||
val totalMessages = messageReader.getMessagesCount()
|
||||
for (threadId in conversationIds) {
|
||||
writer.beginObject()
|
||||
if (config.exportSms && messageReader.getSmsCount() > 0) {
|
||||
writer.name("sms")
|
||||
writer.beginArray()
|
||||
messageReader.forEachSms(threadId) {
|
||||
writer.jsonValue(gson.toJson(it))
|
||||
written++
|
||||
onProgress.invoke(totalMessages, written)
|
||||
}
|
||||
writer.endArray()
|
||||
}
|
||||
|
||||
if (config.exportMms && messageReader.getMmsCount() > 0) {
|
||||
writer.name("mms")
|
||||
writer.beginArray()
|
||||
messageReader.forEachMms(threadId) {
|
||||
writer.jsonValue(gson.toJson(it))
|
||||
written++
|
||||
onProgress.invoke(totalMessages, written)
|
||||
}
|
||||
|
||||
writer.endArray()
|
||||
}
|
||||
|
||||
writer.endObject()
|
||||
}
|
||||
writer.endArray()
|
||||
callback.invoke(ExportResult.EXPORT_OK)
|
||||
} catch (e: Exception) {
|
||||
callback.invoke(ExportResult.EXPORT_FAIL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,172 +1,145 @@
|
||||
package com.simplemobiletools.smsmessenger.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.util.JsonToken
|
||||
import android.net.Uri
|
||||
import android.util.Xml
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
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.helpers.MessagesImporter.ImportResult.IMPORT_FAIL
|
||||
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_NOTHING_NEW
|
||||
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_OK
|
||||
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_PARTIAL
|
||||
import com.simplemobiletools.smsmessenger.models.MmsBackup
|
||||
import com.simplemobiletools.smsmessenger.models.SmsBackup
|
||||
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.File
|
||||
import java.io.InputStream
|
||||
|
||||
class MessagesImporter(private val context: Context) {
|
||||
enum class ImportResult {
|
||||
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL, IMPORT_NOTHING_NEW
|
||||
}
|
||||
|
||||
private val gson = Gson()
|
||||
private val messageWriter = MessagesWriter(context)
|
||||
private val config = context.config
|
||||
class MessagesImporter(private val activity: SimpleActivity) {
|
||||
|
||||
private val messageWriter = MessagesWriter(activity)
|
||||
private val config = activity.config
|
||||
private var messagesImported = 0
|
||||
private var messagesFailed = 0
|
||||
|
||||
fun importMessages(path: String, onProgress: (total: Int, current: Int) -> Unit = { _, _ -> }, 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) {
|
||||
activity.toast(R.string.importing)
|
||||
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<List<MessagesBackup>>(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<MessagesBackup>, callback: (ImportResult) -> Unit) {
|
||||
ensureBackgroundThread {
|
||||
try {
|
||||
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("<?xml")
|
||||
messagesBackup.forEach { message ->
|
||||
try {
|
||||
if (message.backupType == BackupType.SMS && config.importSms) {
|
||||
messageWriter.writeSmsMessage(message as SmsBackup)
|
||||
messagesImported++
|
||||
} else if (message.backupType == BackupType.MMS && config.importMms) {
|
||||
messageWriter.writeMmsMessage(message as MmsBackup)
|
||||
messagesImported++
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
activity.showErrorToast(e)
|
||||
messagesFailed++
|
||||
}
|
||||
} else {
|
||||
path.endsWith("xml")
|
||||
}
|
||||
|
||||
val inputStream = getInputStreamForPath(path)
|
||||
|
||||
if (isXml) {
|
||||
inputStream.importXml()
|
||||
} else {
|
||||
inputStream.importJson()
|
||||
}
|
||||
|
||||
refreshMessages()
|
||||
} catch (e: Exception) {
|
||||
context.showErrorToast(e)
|
||||
messagesFailed++
|
||||
activity.showErrorToast(e)
|
||||
}
|
||||
|
||||
callback.invoke(
|
||||
when {
|
||||
messagesImported == 0 && messagesFailed == 0 -> IMPORT_NOTHING_NEW
|
||||
messagesFailed > 0 && messagesImported > 0 -> IMPORT_PARTIAL
|
||||
messagesFailed > 0 -> IMPORT_FAIL
|
||||
else -> IMPORT_OK
|
||||
messagesImported == 0 && messagesFailed == 0 -> ImportResult.IMPORT_NOTHING_NEW
|
||||
messagesFailed > 0 && messagesImported > 0 -> ImportResult.IMPORT_PARTIAL
|
||||
messagesFailed > 0 -> ImportResult.IMPORT_FAIL
|
||||
else -> ImportResult.IMPORT_OK
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInputStreamForPath(path: String): InputStream {
|
||||
return if (path.contains("/")) {
|
||||
File(path).inputStream()
|
||||
} else {
|
||||
context.assets.open(path)
|
||||
}
|
||||
}
|
||||
|
||||
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++
|
||||
try {
|
||||
bufferedReader().use { reader ->
|
||||
val xmlParser = Xml.newPullParser().apply {
|
||||
setInput(reader)
|
||||
}
|
||||
|
||||
if (xmlParser.eventType != XmlPullParser.START_TAG) {
|
||||
continue
|
||||
}
|
||||
xmlParser.nextTag()
|
||||
xmlParser.require(XmlPullParser.START_TAG, null, "smses")
|
||||
|
||||
try {
|
||||
if (xmlParser.name == "sms") {
|
||||
if (config.importSms) {
|
||||
val message = xmlParser.readSms()
|
||||
messageWriter.writeSmsMessage(message)
|
||||
messagesImported++
|
||||
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()
|
||||
}
|
||||
} else {
|
||||
xmlParser.skip()
|
||||
} catch (e: Exception) {
|
||||
activity.showErrorToast(e)
|
||||
messagesFailed++
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
context.showErrorToast(e)
|
||||
messagesFailed++
|
||||
}
|
||||
refreshMessages()
|
||||
}
|
||||
|
||||
refreshMessages()
|
||||
when {
|
||||
messagesFailed > 0 && messagesImported > 0 -> activity.toast(R.string.importing_some_entries_failed)
|
||||
messagesFailed > 0 -> activity.toast(R.string.importing_failed)
|
||||
else -> activity.toast(R.string.importing_successful)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
activity.toast(R.string.invalid_file_format)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,4 +173,27 @@ class MessagesImporter(private val context: Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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("<?xml") ?: false
|
||||
} ?: false
|
||||
}
|
||||
|
||||
private fun isXmlMimeType(mimeType: String): Boolean {
|
||||
return mimeType.equals("application/xml", ignoreCase = true) || mimeType.equals("text/xml", ignoreCase = true)
|
||||
}
|
||||
|
||||
private fun isJsonMimeType(mimeType: String): Boolean {
|
||||
return mimeType.equals("application/json", ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
@@ -9,15 +9,30 @@ import android.util.Base64
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.isQPlus
|
||||
import com.simplemobiletools.commons.helpers.isRPlus
|
||||
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 com.simplemobiletools.smsmessenger.extensions.getConversationIds
|
||||
import com.simplemobiletools.smsmessenger.models.*
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
class MessagesReader(private val context: Context) {
|
||||
fun forEachSms(threadId: Long, block: (SmsBackup) -> Unit) {
|
||||
|
||||
fun getMessagesToExport(
|
||||
getSms: Boolean, getMms: Boolean, callback: (messages: List<MessagesBackup>) -> Unit
|
||||
) {
|
||||
val conversationIds = context.getConversationIds()
|
||||
var smsMessages = listOf<SmsBackup>()
|
||||
var mmsMessages = listOf<MmsBackup>()
|
||||
|
||||
if (getSms) {
|
||||
smsMessages = getSmsMessages(conversationIds)
|
||||
}
|
||||
if (getMms) {
|
||||
mmsMessages = getMmsMessages(conversationIds)
|
||||
}
|
||||
callback(smsMessages + mmsMessages)
|
||||
}
|
||||
|
||||
private fun getSmsMessages(threadIds: List<Long>): List<SmsBackup> {
|
||||
val projection = arrayOf(
|
||||
Sms.SUBSCRIPTION_ID,
|
||||
Sms.ADDRESS,
|
||||
@@ -33,25 +48,28 @@ class MessagesReader(private val context: Context) {
|
||||
)
|
||||
|
||||
val selection = "${Sms.THREAD_ID} = ?"
|
||||
val selectionArgs = arrayOf(threadId.toString())
|
||||
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.LOCKED)
|
||||
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))
|
||||
val smsList = mutableListOf<SmsBackup>()
|
||||
|
||||
threadIds.map { it.toString() }.forEach { threadId ->
|
||||
context.queryCursor(Sms.CONTENT_URI, projection, selection, arrayOf(threadId)) { 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)
|
||||
smsList.add(SmsBackup(subscriptionId, address, body, date, dateSent, locked, protocol, read, status, type, serviceCenter))
|
||||
}
|
||||
}
|
||||
return smsList
|
||||
}
|
||||
|
||||
// all mms from simple sms are non-text messages
|
||||
fun forEachMms(threadId: Long, includeTextOnlyAttachment: Boolean = false, block: (MmsBackup) -> Unit) {
|
||||
private fun getMmsMessages(threadIds: List<Long>, includeTextOnlyAttachment: Boolean = false): List<MmsBackup> {
|
||||
val projection = arrayOf(
|
||||
Mms._ID,
|
||||
Mms.CREATOR,
|
||||
@@ -71,65 +89,67 @@ class MessagesReader(private val context: Context) {
|
||||
Mms.SUBSCRIPTION_ID,
|
||||
Mms.TRANSACTION_ID
|
||||
)
|
||||
|
||||
val selection = if (includeTextOnlyAttachment) {
|
||||
"${Mms.THREAD_ID} = ? AND ${Mms.TEXT_ONLY} = ?"
|
||||
} else {
|
||||
"${Mms.THREAD_ID} = ?"
|
||||
}
|
||||
val mmsList = mutableListOf<MmsBackup>()
|
||||
|
||||
val selectionArgs = if (includeTextOnlyAttachment) {
|
||||
arrayOf(threadId.toString(), "1")
|
||||
} else {
|
||||
arrayOf(threadId.toString())
|
||||
}
|
||||
threadIds.map { it.toString() }.forEach { threadId ->
|
||||
val selectionArgs = if (includeTextOnlyAttachment) {
|
||||
arrayOf(threadId, "1")
|
||||
} else {
|
||||
arrayOf(threadId)
|
||||
}
|
||||
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)
|
||||
|
||||
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
|
||||
val parts = getParts(mmsId)
|
||||
val addresses = getMmsAddresses(mmsId)
|
||||
mmsList.add(
|
||||
MmsBackup(
|
||||
creator,
|
||||
contentType,
|
||||
deliveryReport,
|
||||
date,
|
||||
dateSent,
|
||||
locked,
|
||||
messageType,
|
||||
messageBox,
|
||||
read,
|
||||
readReport,
|
||||
seen,
|
||||
textOnly,
|
||||
status,
|
||||
subject,
|
||||
subjectCharSet,
|
||||
subscriptionId,
|
||||
transactionId,
|
||||
addresses,
|
||||
parts
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return mmsList
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
|
@@ -0,0 +1,13 @@
|
||||
package com.simplemobiletools.smsmessenger.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class BackupType {
|
||||
@SerialName("sms")
|
||||
SMS,
|
||||
|
||||
@SerialName("mms")
|
||||
MMS,
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package com.simplemobiletools.smsmessenger.models
|
||||
|
||||
enum class ImportResult {
|
||||
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL, IMPORT_NOTHING_NEW
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package com.simplemobiletools.smsmessenger.models
|
||||
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.*
|
||||
|
||||
@Serializable(with = BackupSerializer::class)
|
||||
sealed class MessagesBackup() {
|
||||
@SerialName("backupType")
|
||||
abstract val backupType: BackupType
|
||||
}
|
||||
|
||||
object BackupSerializer :
|
||||
JsonContentPolymorphicSerializer<MessagesBackup>(MessagesBackup::class) {
|
||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out MessagesBackup> {
|
||||
return when (element.jsonObject["backupType"]?.jsonPrimitive?.content) {
|
||||
"sms" -> SmsBackup.serializer()
|
||||
"mms" -> MmsBackup.serializer()
|
||||
else -> throw SerializationException("ERROR: No Serializer found. Serialization failed.")
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,7 +4,9 @@ import android.content.ContentValues
|
||||
import android.provider.Telephony
|
||||
import androidx.core.content.contentValuesOf
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MmsAddress(
|
||||
@SerializedName("address")
|
||||
val address: String,
|
||||
|
@@ -4,7 +4,9 @@ import android.content.ContentValues
|
||||
import android.provider.Telephony
|
||||
import androidx.core.content.contentValuesOf
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MmsBackup(
|
||||
@SerializedName("creator")
|
||||
val creator: String?,
|
||||
@@ -44,7 +46,9 @@ data class MmsBackup(
|
||||
val addresses: List<MmsAddress>,
|
||||
@SerializedName("parts")
|
||||
val parts: List<MmsPart>,
|
||||
) {
|
||||
|
||||
override val backupType: BackupType = BackupType.MMS,
|
||||
): MessagesBackup() {
|
||||
|
||||
fun toContentValues(): ContentValues {
|
||||
return contentValuesOf(
|
||||
|
@@ -4,7 +4,9 @@ import android.content.ContentValues
|
||||
import android.provider.Telephony
|
||||
import androidx.core.content.contentValuesOf
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MmsPart(
|
||||
@SerializedName("cd")
|
||||
val contentDisposition: String?,
|
||||
|
@@ -5,7 +5,9 @@ import android.content.ContentValues
|
||||
import android.provider.Telephony
|
||||
import androidx.core.content.contentValuesOf
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SmsBackup(
|
||||
@SerializedName("sub_id")
|
||||
val subscriptionId: Long,
|
||||
@@ -28,8 +30,10 @@ data class SmsBackup(
|
||||
@SerializedName("type")
|
||||
val type: Int,
|
||||
@SerializedName("service_center")
|
||||
val serviceCenter: String?
|
||||
) {
|
||||
val serviceCenter: String?,
|
||||
|
||||
override val backupType: BackupType = BackupType.SMS,
|
||||
): MessagesBackup() {
|
||||
|
||||
fun toContentValues(): ContentValues {
|
||||
return contentValuesOf(
|
||||
|
@@ -382,6 +382,47 @@
|
||||
android:text="@string/password_protect_whole_app" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/settings_migrating_divider"
|
||||
layout="@layout/divider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_migrating_label"
|
||||
style="@style/SettingsSectionLabelStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/migrating" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/settings_export_messages_holder"
|
||||
style="@style/SettingsHolderTextViewOneLinerStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/settings_export_messages"
|
||||
style="@style/SettingsTextLabelStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/export_messages" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/settings_import_messages_holder"
|
||||
style="@style/SettingsHolderTextViewOneLinerStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/settings_import_messages"
|
||||
style="@style/SettingsTextLabelStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/import_messages" />
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
@@ -14,21 +14,6 @@
|
||||
android:paddingTop="@dimen/activity_margin"
|
||||
android:paddingEnd="@dimen/activity_margin">
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextInputLayout
|
||||
android:id="@+id/export_messages_folder_hint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/activity_margin"
|
||||
android:hint="@string/folder">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/export_messages_folder"
|
||||
style="@style/UnclickableEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.simplemobiletools.commons.views.MyTextInputLayout>
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextInputLayout
|
||||
android:id="@+id/export_messages_filename_hint"
|
||||
android:layout_width="match_parent"
|
||||
|
@@ -3,16 +3,6 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="AppCompatResource,AlwaysShowAction">
|
||||
<item
|
||||
android:id="@+id/import_messages"
|
||||
android:showAsAction="never"
|
||||
android:title="@string/import_messages"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/export_messages"
|
||||
android:showAsAction="never"
|
||||
android:title="@string/export_messages"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/show_archived"
|
||||
android:showAsAction="never"
|
||||
|
Reference in New Issue
Block a user