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'
|
plugins {
|
||||||
apply plugin: 'kotlin-android'
|
id 'com.android.application'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
id 'kotlin-android'
|
||||||
apply plugin: 'kotlin-kapt'
|
id 'kotlin-android-extensions'
|
||||||
|
id 'kotlin-kapt'
|
||||||
|
id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version"
|
||||||
|
}
|
||||||
|
|
||||||
def keystorePropertiesFile = rootProject.file("keystore.properties")
|
def keystorePropertiesFile = rootProject.file("keystore.properties")
|
||||||
def keystoreProperties = new Properties()
|
def keystoreProperties = new Properties()
|
||||||
@@ -71,6 +74,7 @@ dependencies {
|
|||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
implementation 'com.googlecode.ez-vcard:ez-vcard:0.11.3'
|
implementation 'com.googlecode.ez-vcard:ez-vcard:0.11.3'
|
||||||
implementation 'androidx.lifecycle:lifecycle-process:2.5.1'
|
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"
|
kapt "androidx.room:room-compiler:2.5.2"
|
||||||
implementation "androidx.room:room-runtime: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>;
|
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||||
}
|
}
|
||||||
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
-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.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.role.RoleManager
|
import android.app.role.RoleManager
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ShortcutInfo
|
import android.content.pm.ShortcutInfo
|
||||||
import android.content.pm.ShortcutManager
|
import android.content.pm.ShortcutManager
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
import android.graphics.drawable.LayerDrawable
|
import android.graphics.drawable.LayerDrawable
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Telephony
|
import android.provider.Telephony
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import com.simplemobiletools.commons.dialogs.FilePickerDialog
|
|
||||||
import com.simplemobiletools.commons.dialogs.PermissionRequiredDialog
|
import com.simplemobiletools.commons.dialogs.PermissionRequiredDialog
|
||||||
import com.simplemobiletools.commons.extensions.*
|
import com.simplemobiletools.commons.extensions.*
|
||||||
import com.simplemobiletools.commons.helpers.*
|
import com.simplemobiletools.commons.helpers.*
|
||||||
@@ -25,8 +21,6 @@ import com.simplemobiletools.smsmessenger.BuildConfig
|
|||||||
import com.simplemobiletools.smsmessenger.R
|
import com.simplemobiletools.smsmessenger.R
|
||||||
import com.simplemobiletools.smsmessenger.adapters.ConversationsAdapter
|
import com.simplemobiletools.smsmessenger.adapters.ConversationsAdapter
|
||||||
import com.simplemobiletools.smsmessenger.adapters.SearchResultsAdapter
|
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.extensions.*
|
||||||
import com.simplemobiletools.smsmessenger.helpers.*
|
import com.simplemobiletools.smsmessenger.helpers.*
|
||||||
import com.simplemobiletools.smsmessenger.models.Conversation
|
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.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
|
|
||||||
class MainActivity : SimpleActivity() {
|
class MainActivity : SimpleActivity() {
|
||||||
private val MAKE_DEFAULT_APP_REQUEST = 1
|
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 storedTextColor = 0
|
||||||
private var storedFontSize = 0
|
private var storedFontSize = 0
|
||||||
private var lastSearchedText = ""
|
private var lastSearchedText = ""
|
||||||
private var bus: EventBus? = null
|
private var bus: EventBus? = null
|
||||||
private val smsExporter by lazy { MessagesExporter(this) }
|
|
||||||
private var wasProtectionHandled = false
|
private var wasProtectionHandled = false
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
@@ -174,8 +163,6 @@ class MainActivity : SimpleActivity() {
|
|||||||
|
|
||||||
main_menu.getToolbar().setOnMenuItemClickListener { menuItem ->
|
main_menu.getToolbar().setOnMenuItemClickListener { menuItem ->
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
R.id.import_messages -> tryImportMessages()
|
|
||||||
R.id.export_messages -> tryToExportMessages()
|
|
||||||
R.id.more_apps_from_us -> launchMoreAppsFromUsIntent()
|
R.id.more_apps_from_us -> launchMoreAppsFromUsIntent()
|
||||||
R.id.show_archived -> launchArchivedConversations()
|
R.id.show_archived -> launchArchivedConversations()
|
||||||
R.id.settings -> launchSettings()
|
R.id.settings -> launchSettings()
|
||||||
@@ -200,11 +187,6 @@ class MainActivity : SimpleActivity() {
|
|||||||
} else {
|
} else {
|
||||||
finish()
|
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)
|
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)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun refreshMessages(event: Events.RefreshMessages) {
|
fun refreshMessages(event: Events.RefreshMessages) {
|
||||||
initMessenger()
|
initMessenger()
|
||||||
|
@@ -2,21 +2,30 @@ package com.simplemobiletools.smsmessenger.activities
|
|||||||
|
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import com.simplemobiletools.commons.activities.ManageBlockedNumbersActivity
|
import com.simplemobiletools.commons.activities.ManageBlockedNumbersActivity
|
||||||
import com.simplemobiletools.commons.dialogs.*
|
import com.simplemobiletools.commons.dialogs.*
|
||||||
import com.simplemobiletools.commons.extensions.*
|
import com.simplemobiletools.commons.extensions.*
|
||||||
import com.simplemobiletools.commons.helpers.*
|
import com.simplemobiletools.commons.helpers.*
|
||||||
import com.simplemobiletools.commons.models.RadioItem
|
import com.simplemobiletools.commons.models.RadioItem
|
||||||
import com.simplemobiletools.smsmessenger.R
|
import com.simplemobiletools.smsmessenger.R
|
||||||
|
import com.simplemobiletools.smsmessenger.dialogs.ExportMessagesDialog
|
||||||
import com.simplemobiletools.smsmessenger.extensions.config
|
import com.simplemobiletools.smsmessenger.extensions.config
|
||||||
import com.simplemobiletools.smsmessenger.helpers.*
|
import com.simplemobiletools.smsmessenger.helpers.*
|
||||||
|
import com.simplemobiletools.smsmessenger.models.*
|
||||||
import kotlinx.android.synthetic.main.activity_settings.*
|
import kotlinx.android.synthetic.main.activity_settings.*
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class SettingsActivity : SimpleActivity() {
|
class SettingsActivity : SimpleActivity() {
|
||||||
private var blockedNumbersAtPause = -1
|
private var blockedNumbersAtPause = -1
|
||||||
|
private val messagesFileType = "application/json"
|
||||||
|
private val messageImportFileTypes = listOf("application/json", "application/xml", "text/xml")
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
isMaterialActivity = true
|
isMaterialActivity = true
|
||||||
@@ -49,6 +58,8 @@ class SettingsActivity : SimpleActivity() {
|
|||||||
setupLockScreenVisibility()
|
setupLockScreenVisibility()
|
||||||
setupMMSFileSizeLimit()
|
setupMMSFileSizeLimit()
|
||||||
setupAppPasswordProtection()
|
setupAppPasswordProtection()
|
||||||
|
setupMessagesExport()
|
||||||
|
setupMessagesImport()
|
||||||
updateTextColors(settings_nested_scrollview)
|
updateTextColors(settings_nested_scrollview)
|
||||||
|
|
||||||
if (blockedNumbersAtPause != -1 && blockedNumbersAtPause != getBlockedNumbers().hashCode()) {
|
if (blockedNumbersAtPause != -1 && blockedNumbersAtPause != getBlockedNumbers().hashCode()) {
|
||||||
@@ -60,12 +71,63 @@ class SettingsActivity : SimpleActivity() {
|
|||||||
settings_general_settings_label,
|
settings_general_settings_label,
|
||||||
settings_outgoing_messages_label,
|
settings_outgoing_messages_label,
|
||||||
settings_notifications_label,
|
settings_notifications_label,
|
||||||
settings_security_label
|
settings_security_label,
|
||||||
|
settings_migrating_label
|
||||||
).forEach {
|
).forEach {
|
||||||
it.setTextColor(getProperPrimaryColor())
|
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() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
blockedNumbersAtPause = getBlockedNumbers().hashCode()
|
blockedNumbersAtPause = getBlockedNumbers().hashCode()
|
||||||
@@ -98,7 +160,7 @@ class SettingsActivity : SimpleActivity() {
|
|||||||
settings_use_english_holder.setOnClickListener {
|
settings_use_english_holder.setOnClickListener {
|
||||||
settings_use_english.toggle()
|
settings_use_english.toggle()
|
||||||
config.useEnglish = settings_use_english.isChecked
|
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 android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.simplemobiletools.commons.dialogs.FilePickerDialog
|
|
||||||
import com.simplemobiletools.commons.extensions.*
|
import com.simplemobiletools.commons.extensions.*
|
||||||
import com.simplemobiletools.smsmessenger.R
|
import com.simplemobiletools.smsmessenger.R
|
||||||
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
|
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
|
||||||
import com.simplemobiletools.smsmessenger.extensions.config
|
import com.simplemobiletools.smsmessenger.extensions.config
|
||||||
import com.simplemobiletools.smsmessenger.helpers.JSON_FILE_EXTENSION
|
import kotlinx.android.synthetic.main.dialog_export_messages.view.export_messages_filename
|
||||||
import kotlinx.android.synthetic.main.dialog_export_messages.view.*
|
import kotlinx.android.synthetic.main.dialog_export_messages.view.export_mms_checkbox
|
||||||
import java.io.File
|
import kotlinx.android.synthetic.main.dialog_export_messages.view.export_sms_checkbox
|
||||||
|
|
||||||
class ExportMessagesDialog(
|
class ExportMessagesDialog(
|
||||||
private val activity: SimpleActivity,
|
private val activity: SimpleActivity,
|
||||||
private val path: String,
|
private val callback: (fileName: String) -> Unit,
|
||||||
private val hidePath: Boolean,
|
|
||||||
private val callback: (file: File) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
private var realPath = if (path.isEmpty()) activity.internalStoragePath else path
|
|
||||||
private val config = activity.config
|
private val config = activity.config
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val view = (activity.layoutInflater.inflate(R.layout.dialog_export_messages, null) as ViewGroup).apply {
|
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_sms_checkbox.isChecked = config.exportSms
|
||||||
export_mms_checkbox.isChecked = config.exportMms
|
export_mms_checkbox.isChecked = config.exportMms
|
||||||
|
export_messages_filename.setText(
|
||||||
if (hidePath) {
|
activity.getString(R.string.messages) + "_" + activity.getCurrentFormattedDateTime()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activity.getAlertDialogBuilder()
|
activity.getAlertDialogBuilder()
|
||||||
@@ -45,29 +30,17 @@ class ExportMessagesDialog(
|
|||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.apply {
|
.apply {
|
||||||
activity.setupDialogStuff(view, this, R.string.export_messages) { alertDialog ->
|
activity.setupDialogStuff(view, this, R.string.export_messages) { alertDialog ->
|
||||||
alertDialog.showKeyboard(view.export_messages_filename)
|
|
||||||
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
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
|
val filename = view.export_messages_filename.value
|
||||||
when {
|
when {
|
||||||
filename.isEmpty() -> activity.toast(R.string.empty_name)
|
filename.isEmpty() -> activity.toast(R.string.empty_name)
|
||||||
filename.isAValidFilename() -> {
|
filename.isAValidFilename() -> {
|
||||||
val file = File(realPath, "$filename$JSON_FILE_EXTENSION")
|
callback(filename)
|
||||||
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)
|
|
||||||
alertDialog.dismiss()
|
alertDialog.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> activity.toast(R.string.invalid_name)
|
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.activities.SimpleActivity
|
||||||
import com.simplemobiletools.smsmessenger.extensions.config
|
import com.simplemobiletools.smsmessenger.extensions.config
|
||||||
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter
|
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter
|
||||||
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_OK
|
import com.simplemobiletools.smsmessenger.models.MessagesBackup
|
||||||
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_PARTIAL
|
import com.simplemobiletools.smsmessenger.models.ImportResult
|
||||||
import kotlinx.android.synthetic.main.dialog_import_messages.view.*
|
import kotlinx.android.synthetic.main.dialog_import_messages.view.*
|
||||||
|
|
||||||
class ImportMessagesDialog(
|
class ImportMessagesDialog(
|
||||||
private val activity: SimpleActivity,
|
private val activity: SimpleActivity,
|
||||||
private val path: String,
|
private val messages: List<MessagesBackup>,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val config = activity.config
|
private val config = activity.config
|
||||||
@@ -48,7 +48,7 @@ class ImportMessagesDialog(
|
|||||||
config.importSms = view.import_sms_checkbox.isChecked
|
config.importSms = view.import_sms_checkbox.isChecked
|
||||||
config.importMms = view.import_mms_checkbox.isChecked
|
config.importMms = view.import_mms_checkbox.isChecked
|
||||||
ensureBackgroundThread {
|
ensureBackgroundThread {
|
||||||
MessagesImporter(activity).importMessages(path) {
|
MessagesImporter(activity).restoreMessages(messages) {
|
||||||
handleParseResult(it)
|
handleParseResult(it)
|
||||||
alertDialog.dismiss()
|
alertDialog.dismiss()
|
||||||
}
|
}
|
||||||
@@ -58,11 +58,12 @@ class ImportMessagesDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleParseResult(result: MessagesImporter.ImportResult) {
|
private fun handleParseResult(result: ImportResult) {
|
||||||
activity.toast(
|
activity.toast(
|
||||||
when (result) {
|
when (result) {
|
||||||
IMPORT_OK -> R.string.importing_successful
|
ImportResult.IMPORT_OK -> R.string.importing_successful
|
||||||
IMPORT_PARTIAL -> R.string.importing_some_entries_failed
|
ImportResult.IMPORT_PARTIAL -> R.string.importing_some_entries_failed
|
||||||
|
ImportResult.IMPORT_FAIL -> R.string.importing_failed
|
||||||
else -> R.string.no_items_found
|
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,134 +1,100 @@
|
|||||||
package com.simplemobiletools.smsmessenger.helpers
|
package com.simplemobiletools.smsmessenger.helpers
|
||||||
|
|
||||||
import android.content.Context
|
import android.net.Uri
|
||||||
import android.util.JsonToken
|
|
||||||
import android.util.Xml
|
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.showErrorToast
|
||||||
|
import com.simplemobiletools.commons.extensions.toast
|
||||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
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.extensions.config
|
||||||
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_FAIL
|
import com.simplemobiletools.smsmessenger.models.*
|
||||||
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_NOTHING_NEW
|
import kotlinx.serialization.SerializationException
|
||||||
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_OK
|
import kotlinx.serialization.decodeFromString
|
||||||
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_PARTIAL
|
import kotlinx.serialization.json.Json
|
||||||
import com.simplemobiletools.smsmessenger.models.MmsBackup
|
|
||||||
import com.simplemobiletools.smsmessenger.models.SmsBackup
|
|
||||||
import org.xmlpull.v1.XmlPullParser
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import java.io.File
|
|
||||||
import java.io.InputStream
|
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()
|
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 messagesImported = 0
|
||||||
private var messagesFailed = 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 {
|
ensureBackgroundThread {
|
||||||
try {
|
try {
|
||||||
val isXml = if (path.endsWith("txt")) {
|
messagesBackup.forEach { message ->
|
||||||
// Need to read the first line to determine if it is xml
|
try {
|
||||||
val tempStream = getInputStreamForPath(path)
|
if (message.backupType == BackupType.SMS && config.importSms) {
|
||||||
tempStream.bufferedReader().use {
|
messageWriter.writeSmsMessage(message as SmsBackup)
|
||||||
it.readLine().startsWith("<?xml")
|
messagesImported++
|
||||||
|
} else if (message.backupType == BackupType.MMS && config.importMms) {
|
||||||
|
messageWriter.writeMmsMessage(message as MmsBackup)
|
||||||
|
messagesImported++
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
path.endsWith("xml")
|
|
||||||
}
|
|
||||||
|
|
||||||
val inputStream = getInputStreamForPath(path)
|
|
||||||
|
|
||||||
if (isXml) {
|
|
||||||
inputStream.importXml()
|
|
||||||
} else {
|
|
||||||
inputStream.importJson()
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
context.showErrorToast(e)
|
activity.showErrorToast(e)
|
||||||
messagesFailed++
|
messagesFailed++
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
refreshMessages()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
activity.showErrorToast(e)
|
||||||
|
}
|
||||||
|
|
||||||
callback.invoke(
|
callback.invoke(
|
||||||
when {
|
when {
|
||||||
messagesImported == 0 && messagesFailed == 0 -> IMPORT_NOTHING_NEW
|
messagesImported == 0 && messagesFailed == 0 -> ImportResult.IMPORT_NOTHING_NEW
|
||||||
messagesFailed > 0 && messagesImported > 0 -> IMPORT_PARTIAL
|
messagesFailed > 0 && messagesImported > 0 -> ImportResult.IMPORT_PARTIAL
|
||||||
messagesFailed > 0 -> IMPORT_FAIL
|
messagesFailed > 0 -> ImportResult.IMPORT_FAIL
|
||||||
else -> IMPORT_OK
|
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() {
|
private fun InputStream.importXml() {
|
||||||
|
try {
|
||||||
bufferedReader().use { reader ->
|
bufferedReader().use { reader ->
|
||||||
val xmlParser = Xml.newPullParser().apply {
|
val xmlParser = Xml.newPullParser().apply {
|
||||||
setInput(reader)
|
setInput(reader)
|
||||||
@@ -161,13 +127,20 @@ class MessagesImporter(private val context: Context) {
|
|||||||
xmlParser.skip()
|
xmlParser.skip()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
context.showErrorToast(e)
|
activity.showErrorToast(e)
|
||||||
messagesFailed++
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun XmlPullParser.readSms(): SmsBackup {
|
private fun XmlPullParser.readSms(): SmsBackup {
|
||||||
@@ -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.extensions.*
|
||||||
import com.simplemobiletools.commons.helpers.isQPlus
|
import com.simplemobiletools.commons.helpers.isQPlus
|
||||||
import com.simplemobiletools.commons.helpers.isRPlus
|
import com.simplemobiletools.commons.helpers.isRPlus
|
||||||
import com.simplemobiletools.smsmessenger.models.MmsAddress
|
import com.simplemobiletools.smsmessenger.extensions.getConversationIds
|
||||||
import com.simplemobiletools.smsmessenger.models.MmsBackup
|
import com.simplemobiletools.smsmessenger.models.*
|
||||||
import com.simplemobiletools.smsmessenger.models.MmsPart
|
|
||||||
import com.simplemobiletools.smsmessenger.models.SmsBackup
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
class MessagesReader(private val context: Context) {
|
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(
|
val projection = arrayOf(
|
||||||
Sms.SUBSCRIPTION_ID,
|
Sms.SUBSCRIPTION_ID,
|
||||||
Sms.ADDRESS,
|
Sms.ADDRESS,
|
||||||
@@ -33,25 +48,28 @@ class MessagesReader(private val context: Context) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val selection = "${Sms.THREAD_ID} = ?"
|
val selection = "${Sms.THREAD_ID} = ?"
|
||||||
val selectionArgs = arrayOf(threadId.toString())
|
val smsList = mutableListOf<SmsBackup>()
|
||||||
context.queryCursor(Sms.CONTENT_URI, projection, selection, selectionArgs) { cursor ->
|
|
||||||
|
threadIds.map { it.toString() }.forEach { threadId ->
|
||||||
|
context.queryCursor(Sms.CONTENT_URI, projection, selection, arrayOf(threadId)) { cursor ->
|
||||||
val subscriptionId = cursor.getLongValue(Sms.SUBSCRIPTION_ID)
|
val subscriptionId = cursor.getLongValue(Sms.SUBSCRIPTION_ID)
|
||||||
val address = cursor.getStringValue(Sms.ADDRESS)
|
val address = cursor.getStringValue(Sms.ADDRESS)
|
||||||
val body = cursor.getStringValueOrNull(Sms.BODY)
|
val body = cursor.getStringValueOrNull(Sms.BODY)
|
||||||
val date = cursor.getLongValue(Sms.DATE)
|
val date = cursor.getLongValue(Sms.DATE)
|
||||||
val dateSent = cursor.getLongValue(Sms.DATE_SENT)
|
val dateSent = cursor.getLongValue(Sms.DATE_SENT)
|
||||||
val locked = cursor.getIntValue(Sms.LOCKED)
|
val locked = cursor.getIntValue(Sms.DATE_SENT)
|
||||||
val protocol = cursor.getStringValueOrNull(Sms.PROTOCOL)
|
val protocol = cursor.getStringValueOrNull(Sms.PROTOCOL)
|
||||||
val read = cursor.getIntValue(Sms.READ)
|
val read = cursor.getIntValue(Sms.READ)
|
||||||
val status = cursor.getIntValue(Sms.STATUS)
|
val status = cursor.getIntValue(Sms.STATUS)
|
||||||
val type = cursor.getIntValue(Sms.TYPE)
|
val type = cursor.getIntValue(Sms.TYPE)
|
||||||
val serviceCenter = cursor.getStringValueOrNull(Sms.SERVICE_CENTER)
|
val serviceCenter = cursor.getStringValueOrNull(Sms.SERVICE_CENTER)
|
||||||
block(SmsBackup(subscriptionId, address, body, date, dateSent, locked, protocol, read, status, type, serviceCenter))
|
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
|
private fun getMmsMessages(threadIds: List<Long>, includeTextOnlyAttachment: Boolean = false): List<MmsBackup> {
|
||||||
fun forEachMms(threadId: Long, includeTextOnlyAttachment: Boolean = false, block: (MmsBackup) -> Unit) {
|
|
||||||
val projection = arrayOf(
|
val projection = arrayOf(
|
||||||
Mms._ID,
|
Mms._ID,
|
||||||
Mms.CREATOR,
|
Mms.CREATOR,
|
||||||
@@ -71,19 +89,19 @@ class MessagesReader(private val context: Context) {
|
|||||||
Mms.SUBSCRIPTION_ID,
|
Mms.SUBSCRIPTION_ID,
|
||||||
Mms.TRANSACTION_ID
|
Mms.TRANSACTION_ID
|
||||||
)
|
)
|
||||||
|
|
||||||
val selection = if (includeTextOnlyAttachment) {
|
val selection = if (includeTextOnlyAttachment) {
|
||||||
"${Mms.THREAD_ID} = ? AND ${Mms.TEXT_ONLY} = ?"
|
"${Mms.THREAD_ID} = ? AND ${Mms.TEXT_ONLY} = ?"
|
||||||
} else {
|
} else {
|
||||||
"${Mms.THREAD_ID} = ?"
|
"${Mms.THREAD_ID} = ?"
|
||||||
}
|
}
|
||||||
|
val mmsList = mutableListOf<MmsBackup>()
|
||||||
|
|
||||||
|
threadIds.map { it.toString() }.forEach { threadId ->
|
||||||
val selectionArgs = if (includeTextOnlyAttachment) {
|
val selectionArgs = if (includeTextOnlyAttachment) {
|
||||||
arrayOf(threadId.toString(), "1")
|
arrayOf(threadId, "1")
|
||||||
} else {
|
} else {
|
||||||
arrayOf(threadId.toString())
|
arrayOf(threadId)
|
||||||
}
|
}
|
||||||
|
|
||||||
context.queryCursor(Mms.CONTENT_URI, projection, selection, selectionArgs) { cursor ->
|
context.queryCursor(Mms.CONTENT_URI, projection, selection, selectionArgs) { cursor ->
|
||||||
val mmsId = cursor.getLongValue(Mms._ID)
|
val mmsId = cursor.getLongValue(Mms._ID)
|
||||||
val creator = cursor.getStringValueOrNull(Mms.CREATOR)
|
val creator = cursor.getStringValueOrNull(Mms.CREATOR)
|
||||||
@@ -106,7 +124,7 @@ class MessagesReader(private val context: Context) {
|
|||||||
|
|
||||||
val parts = getParts(mmsId)
|
val parts = getParts(mmsId)
|
||||||
val addresses = getMmsAddresses(mmsId)
|
val addresses = getMmsAddresses(mmsId)
|
||||||
block(
|
mmsList.add(
|
||||||
MmsBackup(
|
MmsBackup(
|
||||||
creator,
|
creator,
|
||||||
contentType,
|
contentType,
|
||||||
@@ -131,6 +149,8 @@ class MessagesReader(private val context: Context) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return mmsList
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
private fun getParts(mmsId: Long): List<MmsPart> {
|
private fun getParts(mmsId: Long): List<MmsPart> {
|
||||||
|
@@ -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 android.provider.Telephony
|
||||||
import androidx.core.content.contentValuesOf
|
import androidx.core.content.contentValuesOf
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class MmsAddress(
|
data class MmsAddress(
|
||||||
@SerializedName("address")
|
@SerializedName("address")
|
||||||
val address: String,
|
val address: String,
|
||||||
|
@@ -4,7 +4,9 @@ import android.content.ContentValues
|
|||||||
import android.provider.Telephony
|
import android.provider.Telephony
|
||||||
import androidx.core.content.contentValuesOf
|
import androidx.core.content.contentValuesOf
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class MmsBackup(
|
data class MmsBackup(
|
||||||
@SerializedName("creator")
|
@SerializedName("creator")
|
||||||
val creator: String?,
|
val creator: String?,
|
||||||
@@ -44,7 +46,9 @@ data class MmsBackup(
|
|||||||
val addresses: List<MmsAddress>,
|
val addresses: List<MmsAddress>,
|
||||||
@SerializedName("parts")
|
@SerializedName("parts")
|
||||||
val parts: List<MmsPart>,
|
val parts: List<MmsPart>,
|
||||||
) {
|
|
||||||
|
override val backupType: BackupType = BackupType.MMS,
|
||||||
|
): MessagesBackup() {
|
||||||
|
|
||||||
fun toContentValues(): ContentValues {
|
fun toContentValues(): ContentValues {
|
||||||
return contentValuesOf(
|
return contentValuesOf(
|
||||||
|
@@ -4,7 +4,9 @@ import android.content.ContentValues
|
|||||||
import android.provider.Telephony
|
import android.provider.Telephony
|
||||||
import androidx.core.content.contentValuesOf
|
import androidx.core.content.contentValuesOf
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class MmsPart(
|
data class MmsPart(
|
||||||
@SerializedName("cd")
|
@SerializedName("cd")
|
||||||
val contentDisposition: String?,
|
val contentDisposition: String?,
|
||||||
|
@@ -5,7 +5,9 @@ import android.content.ContentValues
|
|||||||
import android.provider.Telephony
|
import android.provider.Telephony
|
||||||
import androidx.core.content.contentValuesOf
|
import androidx.core.content.contentValuesOf
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class SmsBackup(
|
data class SmsBackup(
|
||||||
@SerializedName("sub_id")
|
@SerializedName("sub_id")
|
||||||
val subscriptionId: Long,
|
val subscriptionId: Long,
|
||||||
@@ -28,8 +30,10 @@ data class SmsBackup(
|
|||||||
@SerializedName("type")
|
@SerializedName("type")
|
||||||
val type: Int,
|
val type: Int,
|
||||||
@SerializedName("service_center")
|
@SerializedName("service_center")
|
||||||
val serviceCenter: String?
|
val serviceCenter: String?,
|
||||||
) {
|
|
||||||
|
override val backupType: BackupType = BackupType.SMS,
|
||||||
|
): MessagesBackup() {
|
||||||
|
|
||||||
fun toContentValues(): ContentValues {
|
fun toContentValues(): ContentValues {
|
||||||
return contentValuesOf(
|
return contentValuesOf(
|
||||||
|
@@ -382,6 +382,47 @@
|
|||||||
android:text="@string/password_protect_whole_app" />
|
android:text="@string/password_protect_whole_app" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</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>
|
</LinearLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
@@ -14,21 +14,6 @@
|
|||||||
android:paddingTop="@dimen/activity_margin"
|
android:paddingTop="@dimen/activity_margin"
|
||||||
android:paddingEnd="@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
|
<com.simplemobiletools.commons.views.MyTextInputLayout
|
||||||
android:id="@+id/export_messages_filename_hint"
|
android:id="@+id/export_messages_filename_hint"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@@ -3,16 +3,6 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
tools:ignore="AppCompatResource,AlwaysShowAction">
|
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
|
<item
|
||||||
android:id="@+id/show_archived"
|
android:id="@+id/show_archived"
|
||||||
android:showAsAction="never"
|
android:showAsAction="never"
|
||||||
|
Reference in New Issue
Block a user