diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9ca3096ea..5eca4365f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -283,9 +283,14 @@
+
+
+
, outputStream: OutputStream?) {
ensureBackgroundThread {
- val events = eventsHelper.getEventsToExport(eventTypes)
+ val events = eventsHelper.getEventsToExport(eventTypes, config.exportEvents, config.exportTasks, config.exportPastEntries)
if (events.isEmpty()) {
toast(R.string.no_entries_for_exporting)
} else {
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt
index d0fda2ba8..21ef9d51e 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt
@@ -11,9 +11,10 @@ import android.widget.Toast
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.dialogs.ManageAutomaticBackupsDialog
import com.simplemobiletools.calendar.pro.dialogs.SelectCalendarsDialog
import com.simplemobiletools.calendar.pro.dialogs.SelectEventTypeDialog
-import com.simplemobiletools.calendar.pro.dialogs.SelectQuickFilterEventTypesDialog
+import com.simplemobiletools.calendar.pro.dialogs.SelectEventTypesDialog
import com.simplemobiletools.calendar.pro.extensions.*
import com.simplemobiletools.calendar.pro.helpers.*
import com.simplemobiletools.calendar.pro.helpers.Formatter
@@ -99,6 +100,8 @@ class SettingsActivity : SimpleActivity() {
setupAllowChangingTimeZones()
updateTextColors(settings_holder)
checkPrimaryColor()
+ setupEnableAutomaticBackups()
+ setupManageAutomaticBackups()
setupExportSettings()
setupImportSettings()
@@ -114,6 +117,7 @@ class SettingsActivity : SimpleActivity() {
settings_widgets_label,
settings_events_label,
settings_tasks_label,
+ settings_backups_label,
settings_migrating_label
).forEach {
it.setTextColor(getProperPrimaryColor())
@@ -334,7 +338,9 @@ class SettingsActivity : SimpleActivity() {
}
private fun showQuickFilterPicker() {
- SelectQuickFilterEventTypesDialog(this)
+ SelectEventTypesDialog(this, config.quickFilterEventTypes) {
+ config.quickFilterEventTypes = it
+ }
}
private fun setupSundayFirst() {
@@ -830,6 +836,46 @@ class SettingsActivity : SimpleActivity() {
}
}
+ private fun setupEnableAutomaticBackups() {
+ settings_backups_label.beVisibleIf(isRPlus())
+ settings_backups_divider.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) {
+ ManageAutomaticBackupsDialog(
+ 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 {
+ ManageAutomaticBackupsDialog(
+ 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)
+ }
+
private fun setupExportSettings() {
settings_export_holder.setOnClickListener {
val configItems = LinkedHashMap().apply {
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DateTimePatternInfoDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DateTimePatternInfoDialog.kt
new file mode 100644
index 000000000..a49859a09
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DateTimePatternInfoDialog.kt
@@ -0,0 +1,18 @@
+package com.simplemobiletools.calendar.pro.dialogs
+
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+
+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)
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt
new file mode 100644
index 000000000..a744f291e
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ManageAutomaticBackupsDialog.kt
@@ -0,0 +1,131 @@
+package com.simplemobiletools.calendar.pro.dialogs
+
+import android.view.ViewGroup
+import androidx.appcompat.app.AlertDialog
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.activities.SimpleActivity
+import com.simplemobiletools.calendar.pro.extensions.config
+import com.simplemobiletools.commons.dialogs.FilePickerDialog
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.ensureBackgroundThread
+import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.*
+import java.io.File
+
+class ManageAutomaticBackupsDialog(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 selectedEventTypes = config.autoBackupEventTypes.ifEmpty { config.displayEventTypes }
+
+ init {
+ view.apply {
+ backup_events_folder.setText(activity.humanizePath(backupFolder))
+ val filename = config.autoBackupFilename.ifEmpty {
+ "${activity.getString(R.string.events)}_%Y%M%D_%h%m%s"
+ }
+
+ backup_events_filename.setText(filename)
+ backup_events_filename_hint.setEndIconOnClickListener {
+ DateTimePatternInfoDialog(activity)
+ }
+
+ backup_events_filename_hint.setEndIconOnLongClickListener {
+ DateTimePatternInfoDialog(activity)
+ true
+ }
+
+ backup_events_checkbox.isChecked = config.autoBackupEvents
+ backup_events_checkbox_holder.setOnClickListener {
+ backup_events_checkbox.toggle()
+ }
+
+ backup_tasks_checkbox.isChecked = config.autoBackupTasks
+ backup_tasks_checkbox_holder.setOnClickListener {
+ backup_tasks_checkbox.toggle()
+ }
+
+ backup_past_events_checkbox.isChecked = config.autoBackupPastEntries
+ backup_past_events_checkbox_holder.setOnClickListener {
+ backup_past_events_checkbox.toggle()
+ }
+
+ backup_events_folder.setOnClickListener {
+ selectBackupFolder()
+ }
+
+ manage_event_types_holder.setOnClickListener {
+ SelectEventTypesDialog(activity, selectedEventTypes) {
+ selectedEventTypes = it
+ }
+ }
+ }
+ 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 {
+ val filename = view.backup_events_filename.value
+ when {
+ filename.isEmpty() -> activity.toast(R.string.empty_name)
+ filename.isAValidFilename() -> {
+ val file = File(backupFolder, "$filename.ics")
+ if (file.exists() && !file.canWrite()) {
+ activity.toast(R.string.name_taken)
+ return@setOnClickListener
+ }
+
+ val backupEventsChecked = view.backup_events_checkbox.isChecked
+ val backupTasksChecked = view.backup_tasks_checkbox.isChecked
+ if (!backupEventsChecked && !backupTasksChecked || selectedEventTypes.isEmpty()) {
+ activity.toast(R.string.no_entries_for_exporting)
+ return@setOnClickListener
+ }
+
+ ensureBackgroundThread {
+ config.apply {
+ autoBackupFolder = backupFolder
+ autoBackupFilename = filename
+ autoBackupEvents = backupEventsChecked
+ autoBackupTasks = backupTasksChecked
+ autoBackupPastEntries = view.backup_past_events_checkbox.isChecked
+ if (autoBackupEventTypes != selectedEventTypes) {
+ autoBackupEventTypes = selectedEventTypes
+ }
+ }
+
+ activity.runOnUiThread {
+ onSuccess()
+ }
+
+ dialog.dismiss()
+ }
+ }
+ else -> activity.toast(R.string.invalid_name)
+ }
+ }
+ }
+ }
+ }
+
+ private fun selectBackupFolder() {
+ activity.hideKeyboard(view.backup_events_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_events_folder.setText(activity.humanizePath(path))
+ }
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/FilterEventTypesDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectEventTypesDialog.kt
similarity index 64%
rename from app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/FilterEventTypesDialog.kt
rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectEventTypesDialog.kt
index 8e8f6da7f..e69a32fae 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/FilterEventTypesDialog.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectEventTypesDialog.kt
@@ -4,23 +4,21 @@ import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.calendar.pro.R
import com.simplemobiletools.calendar.pro.activities.SimpleActivity
import com.simplemobiletools.calendar.pro.adapters.FilterEventTypeAdapter
-import com.simplemobiletools.calendar.pro.extensions.config
import com.simplemobiletools.calendar.pro.extensions.eventsHelper
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
import com.simplemobiletools.commons.extensions.setupDialogStuff
import kotlinx.android.synthetic.main.dialog_filter_event_types.view.*
-class FilterEventTypesDialog(val activity: SimpleActivity, val callback: () -> Unit) {
+class SelectEventTypesDialog(val activity: SimpleActivity, selectedEventTypes: Set, val callback: (HashSet) -> Unit) {
private var dialog: AlertDialog? = null
private val view = activity.layoutInflater.inflate(R.layout.dialog_filter_event_types, null)
init {
activity.eventsHelper.getEventTypes(activity, false) {
- val displayEventTypes = activity.config.displayEventTypes
- view.filter_event_types_list.adapter = FilterEventTypeAdapter(activity, it, displayEventTypes)
+ view.filter_event_types_list.adapter = FilterEventTypeAdapter(activity, it, selectedEventTypes)
activity.getAlertDialogBuilder()
- .setPositiveButton(R.string.ok) { dialogInterface, i -> confirmEventTypes() }
+ .setPositiveButton(R.string.ok) { _, _ -> confirmEventTypes() }
.setNegativeButton(R.string.cancel, null)
.apply {
activity.setupDialogStuff(view, this) { alertDialog ->
@@ -31,11 +29,11 @@ class FilterEventTypesDialog(val activity: SimpleActivity, val callback: () -> U
}
private fun confirmEventTypes() {
- val selectedItems = (view.filter_event_types_list.adapter as FilterEventTypeAdapter).getSelectedItemsList().map { it.toString() }.toHashSet()
- if (activity.config.displayEventTypes != selectedItems) {
- activity.config.displayEventTypes = selectedItems
- callback()
- }
+ val adapter = view.filter_event_types_list.adapter as FilterEventTypeAdapter
+ val selectedItems = adapter.getSelectedItemsList()
+ .map { it.toString() }
+ .toHashSet()
+ callback(selectedItems)
dialog?.dismiss()
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectQuickFilterEventTypesDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectQuickFilterEventTypesDialog.kt
deleted file mode 100644
index 17c29a8dd..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectQuickFilterEventTypesDialog.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.simplemobiletools.calendar.pro.dialogs
-
-import androidx.appcompat.app.AlertDialog
-import com.simplemobiletools.calendar.pro.R
-import com.simplemobiletools.calendar.pro.activities.SimpleActivity
-import com.simplemobiletools.calendar.pro.adapters.FilterEventTypeAdapter
-import com.simplemobiletools.calendar.pro.extensions.config
-import com.simplemobiletools.calendar.pro.extensions.eventsHelper
-import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
-import com.simplemobiletools.commons.extensions.setupDialogStuff
-import kotlinx.android.synthetic.main.dialog_filter_event_types.view.*
-
-class SelectQuickFilterEventTypesDialog(val activity: SimpleActivity) {
- private var dialog: AlertDialog? = null
- private val view = activity.layoutInflater.inflate(R.layout.dialog_filter_event_types, null)
-
- init {
- activity.eventsHelper.getEventTypes(activity, false) {
- val quickFilterEventTypes = activity.config.quickFilterEventTypes
- view.filter_event_types_list.adapter = FilterEventTypeAdapter(activity, it, quickFilterEventTypes)
-
- activity.getAlertDialogBuilder()
- .setPositiveButton(R.string.ok) { dialogInterface, i -> confirmEventTypes() }
- .setNegativeButton(R.string.cancel, null)
- .apply {
- activity.setupDialogStuff(view, this) { alertDialog ->
- dialog = alertDialog
- }
- }
- }
- }
-
- private fun confirmEventTypes() {
- val selectedItems = (view.filter_event_types_list.adapter as FilterEventTypeAdapter).getSelectedItemsList().map {
- it.toString()
- }.toHashSet()
-
- if (activity.config.quickFilterEventTypes != selectedItems) {
- activity.config.quickFilterEventTypes = selectedItems
- }
- dialog?.dismiss()
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt
index 36538d89f..de9e5ad25 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt
@@ -13,6 +13,7 @@ import android.content.res.Resources
import android.database.Cursor
import android.graphics.Bitmap
import android.media.AudioAttributes
+import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Bundle
import android.provider.CalendarContract
@@ -37,6 +38,7 @@ import com.simplemobiletools.calendar.pro.interfaces.EventsDao
import com.simplemobiletools.calendar.pro.interfaces.TasksDao
import com.simplemobiletools.calendar.pro.interfaces.WidgetsDao
import com.simplemobiletools.calendar.pro.models.*
+import com.simplemobiletools.calendar.pro.receivers.AutomaticBackupReceiver
import com.simplemobiletools.calendar.pro.receivers.CalDAVSyncReceiver
import com.simplemobiletools.calendar.pro.receivers.NotificationReceiver
import com.simplemobiletools.calendar.pro.services.MarkCompletedService
@@ -47,6 +49,7 @@ import kotlinx.android.synthetic.main.day_monthly_event_view.view.*
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.joda.time.LocalDate
+import java.io.File
import java.util.*
val Context.config: Config get() = Config.newInstance(applicationContext)
@@ -183,6 +186,103 @@ fun Context.cancelPendingIntent(id: Long) {
PendingIntent.getBroadcast(this, id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE).cancel()
}
+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.checkAndBackupEventsOnBoot() {
+ if (config.autoBackup) {
+ val previousRealBackupTime = config.lastAutoBackupTime
+ val previousScheduledBackupTime = getPreviousAutoBackupTime().seconds()
+ val missedPreviousBackup = previousRealBackupTime < previousScheduledBackupTime
+ if (missedPreviousBackup) {
+ // device was probably off at the scheduled time so backup now
+ backupEventsAndTasks()
+ }
+ }
+}
+
+fun Context.backupEventsAndTasks() {
+ ensureBackgroundThread {
+ val config = config
+ val events = eventsHelper.getEventsToExport(
+ eventTypes = config.autoBackupEventTypes.map { it.toLong() } as ArrayList,
+ exportEvents = config.autoBackupEvents,
+ exportTasks = config.autoBackupTasks,
+ exportPastEntries = config.autoBackupPastEntries
+ )
+ if (events.isEmpty()) {
+ toast(R.string.no_entries_for_exporting)
+ config.lastAutoBackupTime = getNowSeconds()
+ scheduleNextAutomaticBackup()
+ return@ensureBackgroundThread
+ }
+
+ 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()
+ }
+
+ val exportFile = File(outputFolder, "$filename.ics")
+ val outputStream = try {
+ exportFile.outputStream()
+ } catch (e: Exception) {
+ showErrorToast(e)
+ null
+ }
+
+ IcsExporter(this).exportEvents(outputStream, events, showExportingToast = false) { result ->
+ when (result) {
+ IcsExporter.ExportResult.EXPORT_PARTIAL -> toast(R.string.exporting_some_entries_failed)
+ IcsExporter.ExportResult.EXPORT_FAIL -> toast(R.string.exporting_failed)
+ else -> {}
+ }
+ MediaScannerConnection.scanFile(
+ this,
+ arrayOf(exportFile.absolutePath),
+ arrayOf(exportFile.getMimeType())
+ ) { _, _ -> }
+
+ config.lastAutoBackupTime = getNowSeconds()
+ }
+ scheduleNextAutomaticBackup()
+ }
+}
+
fun Context.getRepetitionText(seconds: Int) = when (seconds) {
0 -> getString(R.string.no_repetition)
DAY -> getString(R.string.daily)
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt
index 87726d5ea..09cfdafde 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt
@@ -3,6 +3,8 @@ package com.simplemobiletools.calendar.pro.helpers
import android.content.Context
import android.media.AudioManager
import android.media.RingtoneManager
+import android.os.Environment
+import android.os.Environment.DIRECTORY_DOWNLOADS
import com.simplemobiletools.calendar.pro.R
import com.simplemobiletools.calendar.pro.extensions.config
import com.simplemobiletools.calendar.pro.extensions.scheduleCalDAVSync
@@ -258,4 +260,36 @@ class Config(context: Context) : BaseConfig(context) {
var wasFilteredOutWarningShown: Boolean
get() = prefs.getBoolean(WAS_FILTERED_OUT_WARNING_SHOWN, false)
set(wasFilteredOutWarningShown) = prefs.edit().putBoolean(WAS_FILTERED_OUT_WARNING_SHOWN, wasFilteredOutWarningShown).apply()
+
+ var autoBackup: Boolean
+ get() = prefs.getBoolean(AUTO_BACKUP, false)
+ set(enableAutomaticBackups) = prefs.edit().putBoolean(AUTO_BACKUP, enableAutomaticBackups).apply()
+
+ var autoBackupFolder: String
+ get() = prefs.getString(AUTO_BACKUP_FOLDER, Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS).absolutePath)!!
+ set(autoBackupPath) = prefs.edit().putString(AUTO_BACKUP_FOLDER, autoBackupPath).apply()
+
+ var autoBackupFilename: String
+ get() = prefs.getString(AUTO_BACKUP_FILENAME, "")!!
+ set(autoBackupFilename) = prefs.edit().putString(AUTO_BACKUP_FILENAME, autoBackupFilename).apply()
+
+ var autoBackupEventTypes: Set
+ get() = prefs.getStringSet(AUTO_BACKUP_EVENT_TYPES, HashSet())!!
+ set(autoBackupEventTypes) = prefs.edit().remove(AUTO_BACKUP_EVENT_TYPES).putStringSet(AUTO_BACKUP_EVENT_TYPES, autoBackupEventTypes).apply()
+
+ var autoBackupEvents: Boolean
+ get() = prefs.getBoolean(AUTO_BACKUP_EVENTS, true)
+ set(autoBackupEvents) = prefs.edit().putBoolean(AUTO_BACKUP_EVENTS, autoBackupEvents).apply()
+
+ var autoBackupTasks: Boolean
+ get() = prefs.getBoolean(AUTO_BACKUP_TASKS, true)
+ set(autoBackupTasks) = prefs.edit().putBoolean(AUTO_BACKUP_TASKS, autoBackupTasks).apply()
+
+ var autoBackupPastEntries: Boolean
+ get() = prefs.getBoolean(AUTO_BACKUP_PAST_ENTRIES, true)
+ set(autoBackupPastEntries) = prefs.edit().putBoolean(AUTO_BACKUP_PAST_ENTRIES, autoBackupPastEntries).apply()
+
+ var lastAutoBackupTime: Long
+ get() = prefs.getLong(LAST_AUTO_BACKUP_TIME, 0L)
+ set(lastAutoBackupTime) = prefs.edit().putLong(LAST_AUTO_BACKUP_TIME, lastAutoBackupTime).apply()
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt
index 195a98df0..ef5af39d1 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt
@@ -3,12 +3,14 @@ package com.simplemobiletools.calendar.pro.helpers
import com.simplemobiletools.calendar.pro.activities.EventActivity
import com.simplemobiletools.calendar.pro.activities.TaskActivity
import com.simplemobiletools.commons.helpers.MONTH_SECONDS
+import org.joda.time.DateTime
import java.util.*
const val STORED_LOCALLY_ONLY = 0
const val ROW_COUNT = 6
const val COLUMN_COUNT = 7
const val SCHEDULE_CALDAV_REQUEST_CODE = 10000
+const val AUTOMATIC_BACKUP_REQUEST_CODE = 10001
const val FETCH_INTERVAL = 3 * MONTH_SECONDS
const val MAX_SEARCH_YEAR = 2051218800L // 2035, limit search results for events repeating indefinitely
@@ -72,6 +74,8 @@ const val YEAR = 31536000
const val EVENT_PERIOD_TODAY = -1
const val EVENT_PERIOD_CUSTOM = -2
+const val AUTO_BACKUP_INTERVAL_IN_DAYS = 1
+
const val EVENT_LIST_PERIOD = "event_list_period"
// Shared Preferences
@@ -129,6 +133,14 @@ const val HIGHLIGHT_WEEKENDS_COLOR = "highlight_weekends_color"
const val LAST_USED_EVENT_SPAN = "last_used_event_span"
const val ALLOW_CREATING_TASKS = "allow_creating_tasks"
const val WAS_FILTERED_OUT_WARNING_SHOWN = "was_filtered_out_warning_shown"
+const val AUTO_BACKUP = "auto_backup"
+const val AUTO_BACKUP_FOLDER = "auto_backup_folder"
+const val AUTO_BACKUP_FILENAME = "auto_backup_filename"
+const val AUTO_BACKUP_EVENT_TYPES = "auto_backup_event_types"
+const val AUTO_BACKUP_EVENTS = "auto_backup_events"
+const val AUTO_BACKUP_TASKS = "auto_backup_tasks"
+const val AUTO_BACKUP_PAST_ENTRIES = "auto_backup_past_entries"
+const val LAST_AUTO_BACKUP_TIME = "last_auto_backup_time"
// repeat_rule for monthly and yearly repetition
const val REPEAT_SAME_DAY = 1 // i.e. 25th every month, or 3rd june (if yearly repetition)
@@ -264,3 +276,19 @@ fun getActivityToOpen(isTask: Boolean) = if (isTask) {
fun generateImportId(): String {
return UUID.randomUUID().toString().replace("-", "") + System.currentTimeMillis().toString()
}
+
+// 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)
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt
index bcd00625e..31c635149 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt
@@ -519,22 +519,22 @@ class EventsHelper(val context: Context) {
return events
}
- fun getEventsToExport(eventTypes: ArrayList): ArrayList {
+ fun getEventsToExport(eventTypes: ArrayList, exportEvents: Boolean, exportTasks: Boolean, exportPastEntries: Boolean): ArrayList {
val currTS = getNowSeconds()
var events = ArrayList()
val tasks = ArrayList()
- if (config.exportPastEntries) {
- if (config.exportEvents) {
+ if (exportPastEntries) {
+ if (exportEvents) {
events.addAll(eventsDB.getAllEventsWithTypes(eventTypes))
}
- if (config.exportTasks) {
+ if (exportTasks) {
tasks.addAll(eventsDB.getAllTasksWithTypes(eventTypes))
}
} else {
- if (config.exportEvents) {
+ if (exportEvents) {
events.addAll(eventsDB.getAllFutureEventsWithTypes(currTS, eventTypes))
}
- if (config.exportTasks) {
+ if (exportTasks) {
tasks.addAll(eventsDB.getAllFutureTasksWithTypes(currTS, eventTypes))
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt
index 1c4969b8b..1d2e8c753 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt
@@ -1,5 +1,6 @@
package com.simplemobiletools.calendar.pro.helpers
+import android.content.Context
import android.provider.CalendarContract.Events
import com.simplemobiletools.calendar.pro.R
import com.simplemobiletools.calendar.pro.extensions.calDAVHelper
@@ -9,7 +10,6 @@ import com.simplemobiletools.calendar.pro.helpers.IcsExporter.ExportResult.EXPOR
import com.simplemobiletools.calendar.pro.helpers.IcsExporter.ExportResult.EXPORT_PARTIAL
import com.simplemobiletools.calendar.pro.models.CalDAVCalendar
import com.simplemobiletools.calendar.pro.models.Event
-import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.extensions.writeLn
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
@@ -17,7 +17,7 @@ import java.io.BufferedWriter
import java.io.OutputStream
import java.io.OutputStreamWriter
-class IcsExporter(private val activity: BaseSimpleActivity) {
+class IcsExporter(private val context: Context) {
enum class ExportResult {
EXPORT_FAIL, EXPORT_OK, EXPORT_PARTIAL
}
@@ -26,7 +26,7 @@ class IcsExporter(private val activity: BaseSimpleActivity) {
private var eventsExported = 0
private var eventsFailed = 0
private var calendars = ArrayList()
- private val reminderLabel = activity.getString(R.string.reminder)
+ private val reminderLabel = context.getString(R.string.reminder)
private val exportTime = Formatter.getExportedTime(System.currentTimeMillis())
fun exportEvents(
@@ -41,9 +41,9 @@ class IcsExporter(private val activity: BaseSimpleActivity) {
}
ensureBackgroundThread {
- calendars = activity.calDAVHelper.getCalDAVCalendars("", false)
+ calendars = context.calDAVHelper.getCalDAVCalendars("", false)
if (showExportingToast) {
- activity.toast(R.string.exporting)
+ context.toast(R.string.exporting)
}
object : BufferedWriter(OutputStreamWriter(outputStream, Charsets.UTF_8)) {
@@ -133,8 +133,8 @@ class IcsExporter(private val activity: BaseSimpleActivity) {
writeLn(BEGIN_EVENT)
event.title.replace("\n", "\\n").let { if (it.isNotEmpty()) writeLn("$SUMMARY:$it") }
event.importId.let { if (it.isNotEmpty()) writeLn("$UID$it") }
- writeLn("$CATEGORY_COLOR${activity.eventTypesDB.getEventTypeWithId(event.eventType)?.color}")
- writeLn("$CATEGORIES${activity.eventTypesDB.getEventTypeWithId(event.eventType)?.title}")
+ writeLn("$CATEGORY_COLOR${context.eventTypesDB.getEventTypeWithId(event.eventType)?.color}")
+ writeLn("$CATEGORIES${context.eventTypesDB.getEventTypeWithId(event.eventType)?.title}")
writeLn("$LAST_MODIFIED:${Formatter.getExportedTime(event.lastUpdated)}")
writeLn("$TRANSP${if (event.availability == Events.AVAILABILITY_FREE) TRANSPARENT else OPAQUE}")
event.location.let { if (it.isNotEmpty()) writeLn("$LOCATION:$it") }
@@ -166,8 +166,8 @@ class IcsExporter(private val activity: BaseSimpleActivity) {
writeLn(BEGIN_TASK)
task.title.replace("\n", "\\n").let { if (it.isNotEmpty()) writeLn("$SUMMARY:$it") }
task.importId.let { if (it.isNotEmpty()) writeLn("$UID$it") }
- writeLn("$CATEGORY_COLOR${activity.eventTypesDB.getEventTypeWithId(task.eventType)?.color}")
- writeLn("$CATEGORIES${activity.eventTypesDB.getEventTypeWithId(task.eventType)?.title}")
+ writeLn("$CATEGORY_COLOR${context.eventTypesDB.getEventTypeWithId(task.eventType)?.color}")
+ writeLn("$CATEGORIES${context.eventTypesDB.getEventTypeWithId(task.eventType)?.title}")
writeLn("$LAST_MODIFIED:${Formatter.getExportedTime(task.lastUpdated)}")
task.location.let { if (it.isNotEmpty()) writeLn("$LOCATION:$it") }
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/AutomaticBackupReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/AutomaticBackupReceiver.kt
new file mode 100644
index 000000000..79d717736
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/AutomaticBackupReceiver.kt
@@ -0,0 +1,17 @@
+package com.simplemobiletools.calendar.pro.receivers
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.os.PowerManager
+import com.simplemobiletools.calendar.pro.extensions.backupEventsAndTasks
+
+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, "simplecalendar:automaticbackupreceiver")
+ wakelock.acquire(3000)
+ context.backupEventsAndTasks()
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt
index e6cd7e523..cc50ec102 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt
@@ -3,9 +3,7 @@ package com.simplemobiletools.calendar.pro.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import com.simplemobiletools.calendar.pro.extensions.notifyRunningEvents
-import com.simplemobiletools.calendar.pro.extensions.recheckCalDAVCalendars
-import com.simplemobiletools.calendar.pro.extensions.scheduleAllEvents
+import com.simplemobiletools.calendar.pro.extensions.*
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
class BootCompletedReceiver : BroadcastReceiver() {
@@ -16,6 +14,8 @@ class BootCompletedReceiver : BroadcastReceiver() {
scheduleAllEvents()
notifyRunningEvents()
recheckCalDAVCalendars(true) {}
+ scheduleNextAutomaticBackup()
+ checkAndBackupEventsOnBoot()
}
}
}
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
index 82e4ea7a5..0fa295391 100644
--- a/app/src/main/res/layout/activity_settings.xml
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -921,6 +921,47 @@
android:id="@+id/settings_tasks_divider"
layout="@layout/divider" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_manage_automatic_backups.xml b/app/src/main/res/layout/dialog_manage_automatic_backups.xml
new file mode 100644
index 000000000..3f0c97fcd
--- /dev/null
+++ b/app/src/main/res/layout/dialog_manage_automatic_backups.xml
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+