commit
9bf842c9f7
13 changed files with 569 additions and 11 deletions
|
@ -4,11 +4,15 @@
|
||||||
package="com.simplemobiletools.contacts.pro"
|
package="com.simplemobiletools.contacts.pro"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
@ -206,6 +210,22 @@
|
||||||
android:label="@string/customize_colors"
|
android:label="@string/customize_colors"
|
||||||
android:parentActivityName=".activities.SettingsActivity" />
|
android:parentActivityName=".activities.SettingsActivity" />
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".receivers.BootCompletedReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||||
|
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
||||||
|
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".receivers.AutomaticBackupReceiver"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
|
|
|
@ -6,11 +6,15 @@ 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.contacts.pro.R
|
import com.simplemobiletools.contacts.pro.R
|
||||||
|
import com.simplemobiletools.contacts.pro.dialogs.ManageAutoBackupsDialog
|
||||||
import com.simplemobiletools.contacts.pro.dialogs.ManageVisibleFieldsDialog
|
import com.simplemobiletools.contacts.pro.dialogs.ManageVisibleFieldsDialog
|
||||||
import com.simplemobiletools.contacts.pro.dialogs.ManageVisibleTabsDialog
|
import com.simplemobiletools.contacts.pro.dialogs.ManageVisibleTabsDialog
|
||||||
|
import com.simplemobiletools.contacts.pro.extensions.cancelScheduledAutomaticBackup
|
||||||
import com.simplemobiletools.contacts.pro.extensions.config
|
import com.simplemobiletools.contacts.pro.extensions.config
|
||||||
|
import com.simplemobiletools.contacts.pro.extensions.scheduleNextAutomaticBackup
|
||||||
import kotlinx.android.synthetic.main.activity_settings.*
|
import kotlinx.android.synthetic.main.activity_settings.*
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class SettingsActivity : SimpleActivity() {
|
class SettingsActivity : SimpleActivity() {
|
||||||
|
|
||||||
|
@ -42,9 +46,17 @@ class SettingsActivity : SimpleActivity() {
|
||||||
setupShowPrivateContacts()
|
setupShowPrivateContacts()
|
||||||
setupOnContactClick()
|
setupOnContactClick()
|
||||||
setupDefaultTab()
|
setupDefaultTab()
|
||||||
|
setupEnableAutomaticBackups()
|
||||||
|
setupManageAutomaticBackups()
|
||||||
updateTextColors(settings_holder)
|
updateTextColors(settings_holder)
|
||||||
|
|
||||||
arrayOf(settings_color_customization_section_label, settings_general_settings_label, settings_main_screen_label, settings_list_view_label).forEach {
|
arrayOf(
|
||||||
|
settings_color_customization_section_label,
|
||||||
|
settings_general_settings_label,
|
||||||
|
settings_main_screen_label,
|
||||||
|
settings_list_view_label,
|
||||||
|
settings_backups_label
|
||||||
|
).forEach {
|
||||||
it.setTextColor(getProperPrimaryColor())
|
it.setTextColor(getProperPrimaryColor())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,7 +128,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,4 +227,43 @@ class SettingsActivity : SimpleActivity() {
|
||||||
config.mergeDuplicateContacts = settings_merge_duplicate_contacts.isChecked
|
config.mergeDuplicateContacts = settings_merge_duplicate_contacts.isChecked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupEnableAutomaticBackups() {
|
||||||
|
settings_backups_label.beVisibleIf(isRPlus())
|
||||||
|
settings_enable_automatic_backups_holder.beVisibleIf(isRPlus())
|
||||||
|
settings_enable_automatic_backups.isChecked = config.autoBackup
|
||||||
|
settings_enable_automatic_backups_holder.setOnClickListener {
|
||||||
|
val wasBackupDisabled = !config.autoBackup
|
||||||
|
if (wasBackupDisabled) {
|
||||||
|
ManageAutoBackupsDialog(
|
||||||
|
activity = this,
|
||||||
|
onSuccess = {
|
||||||
|
enableOrDisableAutomaticBackups(true)
|
||||||
|
scheduleNextAutomaticBackup()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
cancelScheduledAutomaticBackup()
|
||||||
|
enableOrDisableAutomaticBackups(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupManageAutomaticBackups() {
|
||||||
|
settings_manage_automatic_backups_holder.beVisibleIf(isRPlus() && config.autoBackup)
|
||||||
|
settings_manage_automatic_backups_holder.setOnClickListener {
|
||||||
|
ManageAutoBackupsDialog(
|
||||||
|
activity = this,
|
||||||
|
onSuccess = {
|
||||||
|
scheduleNextAutomaticBackup()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enableOrDisableAutomaticBackups(enable: Boolean) {
|
||||||
|
config.autoBackup = enable
|
||||||
|
settings_enable_automatic_backups.isChecked = enable
|
||||||
|
settings_manage_automatic_backups_holder.beVisibleIf(enable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import kotlinx.android.synthetic.main.item_filter_contact_source.view.*
|
||||||
class FilterContactSourcesAdapter(
|
class FilterContactSourcesAdapter(
|
||||||
val activity: SimpleActivity,
|
val activity: SimpleActivity,
|
||||||
private val contactSources: List<ContactSource>,
|
private val contactSources: List<ContactSource>,
|
||||||
private val displayContactSources: ArrayList<String>
|
private val displayContactSources: List<String>
|
||||||
) : RecyclerView.Adapter<FilterContactSourcesAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<FilterContactSourcesAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private val selectedKeys = HashSet<Int>()
|
private val selectedKeys = HashSet<Int>()
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.simplemobiletools.contacts.pro.dialogs
|
||||||
|
|
||||||
|
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||||
|
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
|
||||||
|
import com.simplemobiletools.commons.extensions.setupDialogStuff
|
||||||
|
import com.simplemobiletools.contacts.pro.R
|
||||||
|
|
||||||
|
class DateTimePatternInfoDialog(activity: BaseSimpleActivity) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
val view = activity.layoutInflater.inflate(R.layout.datetime_pattern_info_layout, null)
|
||||||
|
activity.getAlertDialogBuilder()
|
||||||
|
.setPositiveButton(R.string.ok) { _, _ -> { } }
|
||||||
|
.apply {
|
||||||
|
activity.setupDialogStuff(view, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
package com.simplemobiletools.contacts.pro.dialogs
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.simplemobiletools.commons.dialogs.FilePickerDialog
|
||||||
|
import com.simplemobiletools.commons.extensions.*
|
||||||
|
import com.simplemobiletools.commons.helpers.ContactsHelper
|
||||||
|
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||||
|
import com.simplemobiletools.commons.models.contacts.Contact
|
||||||
|
import com.simplemobiletools.commons.models.contacts.ContactSource
|
||||||
|
import com.simplemobiletools.contacts.pro.R
|
||||||
|
import com.simplemobiletools.contacts.pro.activities.SimpleActivity
|
||||||
|
import com.simplemobiletools.contacts.pro.adapters.FilterContactSourcesAdapter
|
||||||
|
import com.simplemobiletools.contacts.pro.extensions.config
|
||||||
|
import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.backup_contact_sources_list
|
||||||
|
import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.backup_contacts_filename
|
||||||
|
import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.backup_contacts_filename_hint
|
||||||
|
import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.backup_contacts_folder
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class ManageAutoBackupsDialog(private val activity: SimpleActivity, onSuccess: () -> Unit) {
|
||||||
|
private val view = (activity.layoutInflater.inflate(R.layout.dialog_manage_automatic_backups, null) as ViewGroup)
|
||||||
|
private val config = activity.config
|
||||||
|
private var backupFolder = config.autoBackupFolder
|
||||||
|
private var contactSources = mutableListOf<ContactSource>()
|
||||||
|
private var selectedContactSources = config.autoBackupContactSources
|
||||||
|
private var contacts = ArrayList<Contact>()
|
||||||
|
private var isContactSourcesReady = false
|
||||||
|
private var isContactsReady = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
view.apply {
|
||||||
|
backup_contacts_folder.setText(activity.humanizePath(backupFolder))
|
||||||
|
val filename = config.autoBackupFilename.ifEmpty {
|
||||||
|
"${activity.getString(R.string.contacts)}_%Y%M%D_%h%m%s"
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_contacts_filename.setText(filename)
|
||||||
|
backup_contacts_filename_hint.setEndIconOnClickListener {
|
||||||
|
DateTimePatternInfoDialog(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_contacts_filename_hint.setEndIconOnLongClickListener {
|
||||||
|
DateTimePatternInfoDialog(activity)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_contacts_folder.setOnClickListener {
|
||||||
|
selectBackupFolder()
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactsHelper(activity).getContactSources { sources ->
|
||||||
|
contactSources = sources
|
||||||
|
isContactSourcesReady = true
|
||||||
|
processDataIfReady(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactsHelper(activity).getContacts(getAll = true) { receivedContacts ->
|
||||||
|
contacts = receivedContacts
|
||||||
|
isContactsReady = true
|
||||||
|
processDataIfReady(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.getAlertDialogBuilder()
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.apply {
|
||||||
|
activity.setupDialogStuff(view, this, R.string.manage_automatic_backups) { dialog ->
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||||
|
if (view.backup_contact_sources_list.adapter == null) {
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
val filename = view.backup_contacts_filename.value
|
||||||
|
when {
|
||||||
|
filename.isEmpty() -> activity.toast(R.string.empty_name)
|
||||||
|
filename.isAValidFilename() -> {
|
||||||
|
val file = File(backupFolder, "$filename.vcf")
|
||||||
|
if (file.exists() && !file.canWrite()) {
|
||||||
|
activity.toast(R.string.name_taken)
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectedSources = (view.backup_contact_sources_list.adapter as FilterContactSourcesAdapter).getSelectedContactSources()
|
||||||
|
if (selectedSources.isEmpty()) {
|
||||||
|
activity.toast(R.string.no_entries_for_exporting)
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
config.autoBackupContactSources = selectedSources.map { it.name }.toSet()
|
||||||
|
|
||||||
|
ensureBackgroundThread {
|
||||||
|
config.apply {
|
||||||
|
autoBackupFolder = backupFolder
|
||||||
|
autoBackupFilename = filename
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.runOnUiThread {
|
||||||
|
onSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> activity.toast(R.string.invalid_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processDataIfReady(view: View) {
|
||||||
|
if (!isContactSourcesReady || !isContactsReady) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedContactSources.isEmpty()) {
|
||||||
|
selectedContactSources = contactSources.map { it.name }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
val contactSourcesWithCount = mutableListOf<ContactSource>()
|
||||||
|
for (source in contactSources) {
|
||||||
|
val count = contacts.count { it.source == source.name }
|
||||||
|
contactSourcesWithCount.add(source.copy(count = count))
|
||||||
|
}
|
||||||
|
|
||||||
|
contactSources.clear()
|
||||||
|
contactSources.addAll(contactSourcesWithCount)
|
||||||
|
|
||||||
|
activity.runOnUiThread {
|
||||||
|
view.backup_contact_sources_list.adapter = FilterContactSourcesAdapter(activity, contactSourcesWithCount, selectedContactSources.toList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectBackupFolder() {
|
||||||
|
activity.hideKeyboard(view.backup_contacts_filename)
|
||||||
|
FilePickerDialog(activity, backupFolder, false, showFAB = true) { path ->
|
||||||
|
activity.handleSAFDialog(path) { grantedSAF ->
|
||||||
|
if (!grantedSAF) {
|
||||||
|
return@handleSAFDialog
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.handleSAFDialogSdk30(path) { grantedSAF30 ->
|
||||||
|
if (!grantedSAF30) {
|
||||||
|
return@handleSAFDialogSdk30
|
||||||
|
}
|
||||||
|
|
||||||
|
backupFolder = path
|
||||||
|
view.backup_contacts_folder.setText(activity.humanizePath(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +1,25 @@
|
||||||
package com.simplemobiletools.contacts.pro.extensions
|
package com.simplemobiletools.contacts.pro.extensions
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import androidx.core.app.AlarmManagerCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import com.simplemobiletools.commons.extensions.getCachePhoto
|
import com.simplemobiletools.commons.extensions.*
|
||||||
import com.simplemobiletools.commons.helpers.SIGNAL_PACKAGE
|
import com.simplemobiletools.commons.helpers.*
|
||||||
import com.simplemobiletools.commons.helpers.TELEGRAM_PACKAGE
|
|
||||||
import com.simplemobiletools.commons.helpers.VIBER_PACKAGE
|
|
||||||
import com.simplemobiletools.commons.helpers.WHATSAPP_PACKAGE
|
|
||||||
import com.simplemobiletools.contacts.pro.BuildConfig
|
import com.simplemobiletools.contacts.pro.BuildConfig
|
||||||
import com.simplemobiletools.contacts.pro.R
|
import com.simplemobiletools.contacts.pro.R
|
||||||
|
import com.simplemobiletools.contacts.pro.helpers.AUTOMATIC_BACKUP_REQUEST_CODE
|
||||||
import com.simplemobiletools.contacts.pro.helpers.Config
|
import com.simplemobiletools.contacts.pro.helpers.Config
|
||||||
|
import com.simplemobiletools.contacts.pro.helpers.getNextAutoBackupTime
|
||||||
|
import com.simplemobiletools.contacts.pro.helpers.getPreviousAutoBackupTime
|
||||||
|
import com.simplemobiletools.contacts.pro.receivers.AutomaticBackupReceiver
|
||||||
|
import org.joda.time.DateTime
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
val Context.config: Config get() = Config.newInstance(applicationContext)
|
val Context.config: Config get() = Config.newInstance(applicationContext)
|
||||||
fun Context.getCachePhotoUri(file: File = getCachePhoto()) = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.provider", file)
|
fun Context.getCachePhotoUri(file: File = getCachePhoto()) = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.provider", file)
|
||||||
|
@ -29,3 +36,113 @@ fun Context.getPackageDrawable(packageName: String): Drawable {
|
||||||
}, theme
|
}, theme
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Context.getAutomaticBackupIntent(): PendingIntent {
|
||||||
|
val intent = Intent(this, AutomaticBackupReceiver::class.java)
|
||||||
|
return PendingIntent.getBroadcast(this, AUTOMATIC_BACKUP_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.scheduleNextAutomaticBackup() {
|
||||||
|
if (config.autoBackup) {
|
||||||
|
val backupAtMillis = getNextAutoBackupTime().millis
|
||||||
|
val pendingIntent = getAutomaticBackupIntent()
|
||||||
|
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
try {
|
||||||
|
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, backupAtMillis, pendingIntent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
showErrorToast(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.cancelScheduledAutomaticBackup() {
|
||||||
|
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
alarmManager.cancel(getAutomaticBackupIntent())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.checkAndBackupContactsOnBoot() {
|
||||||
|
if (config.autoBackup) {
|
||||||
|
val previousRealBackupTime = config.lastAutoBackupTime
|
||||||
|
val previousScheduledBackupTime = getPreviousAutoBackupTime().millis
|
||||||
|
val missedPreviousBackup = previousRealBackupTime < previousScheduledBackupTime
|
||||||
|
if (missedPreviousBackup) {
|
||||||
|
// device was probably off at the scheduled time so backup now
|
||||||
|
backupContacts()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.backupContacts() {
|
||||||
|
require(isRPlus())
|
||||||
|
ensureBackgroundThread {
|
||||||
|
val config = config
|
||||||
|
ContactsHelper(this).getContactsToExport(selectedContactSources = config.autoBackupContactSources) { contactsToBackup ->
|
||||||
|
if (contactsToBackup.isEmpty()) {
|
||||||
|
toast(R.string.no_entries_for_exporting)
|
||||||
|
config.lastAutoBackupTime = DateTime.now().millis
|
||||||
|
scheduleNextAutomaticBackup()
|
||||||
|
return@getContactsToExport
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val now = DateTime.now()
|
||||||
|
val year = now.year.toString()
|
||||||
|
val month = now.monthOfYear.ensureTwoDigits()
|
||||||
|
val day = now.dayOfMonth.ensureTwoDigits()
|
||||||
|
val hours = now.hourOfDay.ensureTwoDigits()
|
||||||
|
val minutes = now.minuteOfHour.ensureTwoDigits()
|
||||||
|
val seconds = now.secondOfMinute.ensureTwoDigits()
|
||||||
|
|
||||||
|
val filename = config.autoBackupFilename
|
||||||
|
.replace("%Y", year, false)
|
||||||
|
.replace("%M", month, false)
|
||||||
|
.replace("%D", day, false)
|
||||||
|
.replace("%h", hours, false)
|
||||||
|
.replace("%m", minutes, false)
|
||||||
|
.replace("%s", seconds, false)
|
||||||
|
|
||||||
|
val outputFolder = File(config.autoBackupFolder).apply {
|
||||||
|
mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
var exportFile = File(outputFolder, "$filename.vcf")
|
||||||
|
var exportFilePath = exportFile.absolutePath
|
||||||
|
val outputStream = try {
|
||||||
|
if (hasProperStoredFirstParentUri(exportFilePath)) {
|
||||||
|
val exportFileUri = createDocumentUriUsingFirstParentTreeUri(exportFilePath)
|
||||||
|
if (!getDoesFilePathExist(exportFilePath)) {
|
||||||
|
createSAFFileSdk30(exportFilePath)
|
||||||
|
}
|
||||||
|
applicationContext.contentResolver.openOutputStream(exportFileUri, "wt") ?: FileOutputStream(exportFile)
|
||||||
|
} else {
|
||||||
|
var num = 0
|
||||||
|
while (getDoesFilePathExist(exportFilePath) && !exportFile.canWrite()) {
|
||||||
|
num++
|
||||||
|
exportFile = File(outputFolder, "${filename}_${num}.vcf")
|
||||||
|
exportFilePath = exportFile.absolutePath
|
||||||
|
}
|
||||||
|
FileOutputStream(exportFile)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
showErrorToast(e)
|
||||||
|
scheduleNextAutomaticBackup()
|
||||||
|
return@getContactsToExport
|
||||||
|
}
|
||||||
|
|
||||||
|
val exportResult = try {
|
||||||
|
ContactsHelper(this).exportContacts(contactsToBackup, outputStream)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
showErrorToast(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (exportResult) {
|
||||||
|
ExportResult.EXPORT_OK -> toast(R.string.exporting_successful)
|
||||||
|
else -> toast(R.string.exporting_failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.lastAutoBackupTime = DateTime.now().millis
|
||||||
|
scheduleNextAutomaticBackup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,4 +12,9 @@ class Config(context: Context) : BaseConfig(context) {
|
||||||
var showTabs: Int
|
var showTabs: Int
|
||||||
get() = prefs.getInt(SHOW_TABS, ALL_TABS_MASK)
|
get() = prefs.getInt(SHOW_TABS, ALL_TABS_MASK)
|
||||||
set(showTabs) = prefs.edit().putInt(SHOW_TABS, showTabs).apply()
|
set(showTabs) = prefs.edit().putInt(SHOW_TABS, showTabs).apply()
|
||||||
|
|
||||||
|
var autoBackupContactSources: Set<String>
|
||||||
|
get() = prefs.getStringSet(AUTO_BACKUP_CONTACT_SOURCES, setOf())!!
|
||||||
|
set(autoBackupContactSources) = prefs.edit().remove(AUTO_BACKUP_CONTACT_SOURCES).putStringSet(AUTO_BACKUP_CONTACT_SOURCES, autoBackupContactSources).apply()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,20 @@ package com.simplemobiletools.contacts.pro.helpers
|
||||||
import com.simplemobiletools.commons.helpers.TAB_CONTACTS
|
import com.simplemobiletools.commons.helpers.TAB_CONTACTS
|
||||||
import com.simplemobiletools.commons.helpers.TAB_FAVORITES
|
import com.simplemobiletools.commons.helpers.TAB_FAVORITES
|
||||||
import com.simplemobiletools.commons.helpers.TAB_GROUPS
|
import com.simplemobiletools.commons.helpers.TAB_GROUPS
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
|
||||||
const val GROUP = "group"
|
const val GROUP = "group"
|
||||||
const val IS_FROM_SIMPLE_CONTACTS = "is_from_simple_contacts"
|
const val IS_FROM_SIMPLE_CONTACTS = "is_from_simple_contacts"
|
||||||
const val ADD_NEW_CONTACT_NUMBER = "add_new_contact_number"
|
const val ADD_NEW_CONTACT_NUMBER = "add_new_contact_number"
|
||||||
const val FIRST_CONTACT_ID = 1000000
|
|
||||||
const val FIRST_GROUP_ID = 10000L
|
|
||||||
const val DEFAULT_FILE_NAME = "contacts.vcf"
|
const val DEFAULT_FILE_NAME = "contacts.vcf"
|
||||||
const val AVOID_CHANGING_TEXT_TAG = "avoid_changing_text_tag"
|
const val AVOID_CHANGING_TEXT_TAG = "avoid_changing_text_tag"
|
||||||
const val AVOID_CHANGING_VISIBILITY_TAG = "avoid_changing_visibility_tag"
|
const val AVOID_CHANGING_VISIBILITY_TAG = "avoid_changing_visibility_tag"
|
||||||
|
|
||||||
|
const val AUTOMATIC_BACKUP_REQUEST_CODE = 10001
|
||||||
|
const val AUTO_BACKUP_INTERVAL_IN_DAYS = 1
|
||||||
|
|
||||||
|
const val AUTO_BACKUP_CONTACT_SOURCES = "auto_backup_contact_sources"
|
||||||
|
|
||||||
// extras used at third party intents
|
// extras used at third party intents
|
||||||
const val KEY_NAME = "name"
|
const val KEY_NAME = "name"
|
||||||
const val KEY_EMAIL = "email"
|
const val KEY_EMAIL = "email"
|
||||||
|
@ -53,3 +57,20 @@ const val SIGNAL = "signal"
|
||||||
const val VIBER = "viber"
|
const val VIBER = "viber"
|
||||||
const val TELEGRAM = "telegram"
|
const val TELEGRAM = "telegram"
|
||||||
const val THREEMA = "threema"
|
const val THREEMA = "threema"
|
||||||
|
|
||||||
|
|
||||||
|
// 6 am is the hardcoded automatic backup time, intervals shorter than 1 day are not yet supported.
|
||||||
|
fun getNextAutoBackupTime(): DateTime {
|
||||||
|
val now = DateTime.now()
|
||||||
|
val sixHour = now.withHourOfDay(6)
|
||||||
|
return if (now.millis < sixHour.millis) {
|
||||||
|
sixHour
|
||||||
|
} else {
|
||||||
|
sixHour.plusDays(AUTO_BACKUP_INTERVAL_IN_DAYS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPreviousAutoBackupTime(): DateTime {
|
||||||
|
val nextBackupTime = getNextAutoBackupTime()
|
||||||
|
return nextBackupTime.minusDays(AUTO_BACKUP_INTERVAL_IN_DAYS)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.simplemobiletools.contacts.pro.receivers
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.PowerManager
|
||||||
|
import com.simplemobiletools.contacts.pro.extensions.backupContacts
|
||||||
|
|
||||||
|
class AutomaticBackupReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
val wakelock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "simplecontacts:automaticbackupreceiver")
|
||||||
|
wakelock.acquire(3000)
|
||||||
|
context.backupContacts()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.simplemobiletools.contacts.pro.receivers
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||||
|
import com.simplemobiletools.contacts.pro.extensions.checkAndBackupContactsOnBoot
|
||||||
|
|
||||||
|
class BootCompletedReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
ensureBackgroundThread {
|
||||||
|
context.apply {
|
||||||
|
checkAndBackupContactsOnBoot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -341,6 +341,48 @@
|
||||||
android:text="@string/start_name_with_surname" />
|
android:text="@string/start_name_with_surname" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/settings_tasks_divider"
|
||||||
|
layout="@layout/divider" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/settings_backups_label"
|
||||||
|
style="@style/SettingsSectionLabelStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/backups" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/settings_enable_automatic_backups_holder"
|
||||||
|
style="@style/SettingsHolderCheckboxStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
|
||||||
|
android:id="@+id/settings_enable_automatic_backups"
|
||||||
|
style="@style/SettingsCheckboxStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/enable_automatic_backups" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/settings_manage_automatic_backups_holder"
|
||||||
|
style="@style/SettingsHolderTextViewOneLinerStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.simplemobiletools.commons.views.MyTextView
|
||||||
|
android:id="@+id/settings_manage_automatic_backups"
|
||||||
|
style="@style/SettingsTextLabelStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/manage_automatic_backups" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
9
app/src/main/res/layout/datetime_pattern_info_layout.xml
Normal file
9
app/src/main/res/layout/datetime_pattern_info_layout.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.simplemobiletools.commons.views.MyTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/date_time_pattern_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingHorizontal="@dimen/big_margin"
|
||||||
|
android:paddingTop="@dimen/big_margin"
|
||||||
|
android:text="@string/date_time_pattern_info"
|
||||||
|
android:textIsSelectable="true" />
|
83
app/src/main/res/layout/dialog_manage_automatic_backups.xml
Normal file
83
app/src/main/res/layout/dialog_manage_automatic_backups.xml
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/backup_contacts_scrollview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/backup_contacts_holder"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="@dimen/activity_margin">
|
||||||
|
|
||||||
|
<com.simplemobiletools.commons.views.MyTextInputLayout
|
||||||
|
android:id="@+id/backup_contacts_folder_hint"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/activity_margin"
|
||||||
|
android:layout_marginEnd="@dimen/activity_margin"
|
||||||
|
android:layout_marginBottom="@dimen/activity_margin"
|
||||||
|
android:hint="@string/folder">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/backup_contacts_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/backup_contacts_filename_hint"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/activity_margin"
|
||||||
|
android:layout_marginEnd="@dimen/activity_margin"
|
||||||
|
android:hint="@string/filename_without_vcf"
|
||||||
|
app:endIconDrawable="@drawable/ic_info_vector"
|
||||||
|
app:endIconMode="custom">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/backup_contacts_filename"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textCapWords"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textCursorDrawable="@null"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
</com.simplemobiletools.commons.views.MyTextInputLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/backup_contacts_divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:layout_marginStart="@dimen/activity_margin"
|
||||||
|
android:layout_marginTop="@dimen/medium_margin"
|
||||||
|
android:layout_marginEnd="@dimen/activity_margin"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:background="@color/divider_grey"
|
||||||
|
android:importantForAccessibility="no" />
|
||||||
|
|
||||||
|
<com.simplemobiletools.commons.views.MyTextView
|
||||||
|
android:id="@+id/backup_contacts_pick_sources_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/activity_margin"
|
||||||
|
android:paddingEnd="@dimen/activity_margin"
|
||||||
|
android:text="@string/include_contact_sources"
|
||||||
|
android:textSize="@dimen/smaller_text_size" />
|
||||||
|
|
||||||
|
<com.simplemobiletools.commons.views.MyRecyclerView
|
||||||
|
android:id="@+id/backup_contact_sources_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:overScrollMode="never"
|
||||||
|
android:paddingTop="@dimen/medium_margin"
|
||||||
|
app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
Loading…
Reference in a new issue