mirror of
				https://github.com/SimpleMobileTools/Simple-Notes.git
				synced 2025-06-05 17:00:23 +02:00 
			
		
		
		
	Merge pull request #653 from Merkost/automatic_backups
Automatic backups
This commit is contained in:
		| @@ -66,7 +66,7 @@ android { | |||||||
| } | } | ||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|     implementation 'com.github.SimpleMobileTools:Simple-Commons:f737f6c38b' |     implementation 'com.github.SimpleMobileTools:Simple-Commons:91763fe00f' | ||||||
|     implementation 'androidx.constraintlayout:constraintlayout:2.1.4' |     implementation 'androidx.constraintlayout:constraintlayout:2.1.4' | ||||||
|     implementation 'androidx.documentfile:documentfile:1.0.1' |     implementation 'androidx.documentfile:documentfile:1.0.1' | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,10 @@ | |||||||
|     <uses-permission |     <uses-permission | ||||||
|         android:name="android.permission.WRITE_EXTERNAL_STORAGE" |         android:name="android.permission.WRITE_EXTERNAL_STORAGE" | ||||||
|         android:maxSdkVersion="28" /> |         android:maxSdkVersion="28" /> | ||||||
|  |     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> | ||||||
|  |     <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-feature |     <uses-feature | ||||||
|         android:name="android.hardware.faketouch" |         android:name="android.hardware.faketouch" | ||||||
| @@ -107,6 +111,22 @@ | |||||||
|                 android:resource="@xml/widget_info" /> |                 android:resource="@xml/widget_info" /> | ||||||
|         </receiver> |         </receiver> | ||||||
|  |  | ||||||
|  |         <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" /> | ||||||
|  |  | ||||||
|         <activity-alias |         <activity-alias | ||||||
|             android:name=".activities.SplashActivity.Red" |             android:name=".activities.SplashActivity.Red" | ||||||
|             android:enabled="false" |             android:enabled="false" | ||||||
|   | |||||||
| @@ -13,10 +13,8 @@ import com.simplemobiletools.commons.helpers.* | |||||||
| import com.simplemobiletools.commons.models.RadioItem | import com.simplemobiletools.commons.models.RadioItem | ||||||
| import com.simplemobiletools.notes.pro.R | import com.simplemobiletools.notes.pro.R | ||||||
| import com.simplemobiletools.notes.pro.dialogs.ExportNotesDialog | import com.simplemobiletools.notes.pro.dialogs.ExportNotesDialog | ||||||
| import com.simplemobiletools.notes.pro.extensions.config | import com.simplemobiletools.notes.pro.dialogs.ManageAutoBackupsDialog | ||||||
| import com.simplemobiletools.notes.pro.extensions.requestUnlockNotes | import com.simplemobiletools.notes.pro.extensions.* | ||||||
| import com.simplemobiletools.notes.pro.extensions.updateWidgets |  | ||||||
| import com.simplemobiletools.notes.pro.extensions.widgetsDB |  | ||||||
| import com.simplemobiletools.notes.pro.helpers.* | import com.simplemobiletools.notes.pro.helpers.* | ||||||
| import com.simplemobiletools.notes.pro.models.Note | import com.simplemobiletools.notes.pro.models.Note | ||||||
| import com.simplemobiletools.notes.pro.models.Widget | import com.simplemobiletools.notes.pro.models.Widget | ||||||
| @@ -62,6 +60,8 @@ class SettingsActivity : SimpleActivity() { | |||||||
|         setupCustomizeWidgetColors() |         setupCustomizeWidgetColors() | ||||||
|         setupNotesExport() |         setupNotesExport() | ||||||
|         setupNotesImport() |         setupNotesImport() | ||||||
|  |         setupEnableAutomaticBackups() | ||||||
|  |         setupManageAutomaticBackups() | ||||||
|         updateTextColors(settings_nested_scrollview) |         updateTextColors(settings_nested_scrollview) | ||||||
|  |  | ||||||
|         arrayOf( |         arrayOf( | ||||||
| @@ -71,6 +71,7 @@ class SettingsActivity : SimpleActivity() { | |||||||
|             settings_startup_label, |             settings_startup_label, | ||||||
|             settings_saving_label, |             settings_saving_label, | ||||||
|             settings_migrating_label, |             settings_migrating_label, | ||||||
|  |             settings_backups_label, | ||||||
|         ).forEach { |         ).forEach { | ||||||
|             it.setTextColor(getProperPrimaryColor()) |             it.setTextColor(getProperPrimaryColor()) | ||||||
|         } |         } | ||||||
| @@ -355,4 +356,43 @@ class SettingsActivity : SimpleActivity() { | |||||||
|             showErrorToast(e) |             showErrorToast(e) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     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) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | package com.simplemobiletools.notes.pro.dialogs | ||||||
|  |  | ||||||
|  | import com.simplemobiletools.commons.activities.BaseSimpleActivity | ||||||
|  | import com.simplemobiletools.commons.extensions.getAlertDialogBuilder | ||||||
|  | import com.simplemobiletools.commons.extensions.setupDialogStuff | ||||||
|  | import com.simplemobiletools.notes.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,99 @@ | |||||||
|  | package com.simplemobiletools.notes.pro.dialogs | ||||||
|  |  | ||||||
|  | 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.ensureBackgroundThread | ||||||
|  | import com.simplemobiletools.notes.pro.R | ||||||
|  | import com.simplemobiletools.notes.pro.activities.SimpleActivity | ||||||
|  | import com.simplemobiletools.notes.pro.extensions.config | ||||||
|  | import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.backup_notes_filename | ||||||
|  | import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.backup_notes_filename_hint | ||||||
|  | import kotlinx.android.synthetic.main.dialog_manage_automatic_backups.view.backup_notes_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 | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |         view.apply { | ||||||
|  |             backup_notes_folder.setText(activity.humanizePath(backupFolder)) | ||||||
|  |             val filename = config.autoBackupFilename.ifEmpty { | ||||||
|  |                 "${activity.getString(R.string.notes)}_%Y%M%D_%h%m%s" | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             backup_notes_filename.setText(filename) | ||||||
|  |             backup_notes_filename_hint.setEndIconOnClickListener { | ||||||
|  |                 DateTimePatternInfoDialog(activity) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             backup_notes_filename_hint.setEndIconOnLongClickListener { | ||||||
|  |                 DateTimePatternInfoDialog(activity) | ||||||
|  |                 true | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             backup_notes_folder.setOnClickListener { | ||||||
|  |                 selectBackupFolder() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         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_notes_filename.value | ||||||
|  |                         when { | ||||||
|  |                             filename.isEmpty() -> activity.toast(R.string.empty_name) | ||||||
|  |                             filename.isAValidFilename() -> { | ||||||
|  |                                 val file = File(backupFolder, "$filename.json") | ||||||
|  |                                 if (file.exists() && !file.canWrite()) { | ||||||
|  |                                     activity.toast(R.string.name_taken) | ||||||
|  |                                     return@setOnClickListener | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 ensureBackgroundThread { | ||||||
|  |                                     config.apply { | ||||||
|  |                                         autoBackupFolder = backupFolder | ||||||
|  |                                         autoBackupFilename = filename | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     activity.runOnUiThread { | ||||||
|  |                                         onSuccess() | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     dialog.dismiss() | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             else -> activity.toast(R.string.invalid_name) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun selectBackupFolder() { | ||||||
|  |         activity.hideKeyboard(view.backup_notes_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_notes_folder.setText(activity.humanizePath(path)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,18 +1,28 @@ | |||||||
| package com.simplemobiletools.notes.pro.extensions | package com.simplemobiletools.notes.pro.extensions | ||||||
|  |  | ||||||
|  | import android.app.AlarmManager | ||||||
|  | import android.app.PendingIntent | ||||||
| import android.appwidget.AppWidgetManager | import android.appwidget.AppWidgetManager | ||||||
| import android.content.ComponentName | import android.content.ComponentName | ||||||
| import android.content.Context | import android.content.Context | ||||||
| import android.content.Intent | import android.content.Intent | ||||||
|  | import androidx.core.app.AlarmManagerCompat | ||||||
| import com.simplemobiletools.commons.activities.BaseSimpleActivity | import com.simplemobiletools.commons.activities.BaseSimpleActivity | ||||||
|  | import com.simplemobiletools.commons.extensions.* | ||||||
|  | import com.simplemobiletools.commons.helpers.ExportResult | ||||||
|  | import com.simplemobiletools.commons.helpers.ensureBackgroundThread | ||||||
|  | import com.simplemobiletools.commons.helpers.isRPlus | ||||||
| import com.simplemobiletools.notes.pro.R | import com.simplemobiletools.notes.pro.R | ||||||
| import com.simplemobiletools.notes.pro.databases.NotesDatabase | import com.simplemobiletools.notes.pro.databases.NotesDatabase | ||||||
| import com.simplemobiletools.notes.pro.dialogs.UnlockNotesDialog | import com.simplemobiletools.notes.pro.dialogs.UnlockNotesDialog | ||||||
| import com.simplemobiletools.notes.pro.helpers.Config | import com.simplemobiletools.notes.pro.helpers.* | ||||||
| import com.simplemobiletools.notes.pro.helpers.MyWidgetProvider |  | ||||||
| import com.simplemobiletools.notes.pro.interfaces.NotesDao | import com.simplemobiletools.notes.pro.interfaces.NotesDao | ||||||
| import com.simplemobiletools.notes.pro.interfaces.WidgetsDao | import com.simplemobiletools.notes.pro.interfaces.WidgetsDao | ||||||
| import com.simplemobiletools.notes.pro.models.Note | import com.simplemobiletools.notes.pro.models.Note | ||||||
|  | import com.simplemobiletools.notes.pro.receivers.AutomaticBackupReceiver | ||||||
|  | import org.joda.time.DateTime | ||||||
|  | import java.io.File | ||||||
|  | import java.io.FileOutputStream | ||||||
|  |  | ||||||
| val Context.config: Config get() = Config.newInstance(applicationContext) | val Context.config: Config get() = Config.newInstance(applicationContext) | ||||||
|  |  | ||||||
| @@ -43,3 +53,111 @@ fun BaseSimpleActivity.requestUnlockNotes(notes: List<Note>, callback: (unlocked | |||||||
|         callback(emptyList()) |         callback(emptyList()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | 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.checkAndBackupNotesOnBoot() { | ||||||
|  |     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 | ||||||
|  |             backupNotes() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun Context.backupNotes() { | ||||||
|  |     require(isRPlus()) | ||||||
|  |     ensureBackgroundThread { | ||||||
|  |         val config = config | ||||||
|  |         NotesHelper(this).getNotes { notesToBackup -> | ||||||
|  |             if (notesToBackup.isEmpty()) { | ||||||
|  |                 toast(R.string.no_entries_for_exporting) | ||||||
|  |                 config.lastAutoBackupTime = DateTime.now().millis | ||||||
|  |                 scheduleNextAutomaticBackup() | ||||||
|  |                 return@getNotes | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             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.json") | ||||||
|  |             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}.json") | ||||||
|  |                         exportFilePath = exportFile.absolutePath | ||||||
|  |                     } | ||||||
|  |                     FileOutputStream(exportFile) | ||||||
|  |                 } | ||||||
|  |             } catch (e: Exception) { | ||||||
|  |                 showErrorToast(e) | ||||||
|  |                 scheduleNextAutomaticBackup() | ||||||
|  |                 return@getNotes | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             val exportResult = try { | ||||||
|  |                 NotesHelper(this).exportNotes(notesToBackup, outputStream) | ||||||
|  |             } catch (e: Exception) { | ||||||
|  |                 showErrorToast(e) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (exportResult == ExportResult.EXPORT_FAIL) { | ||||||
|  |                 toast(R.string.exporting_failed) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             config.lastAutoBackupTime = DateTime.now().millis | ||||||
|  |             scheduleNextAutomaticBackup() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package com.simplemobiletools.notes.pro.helpers | package com.simplemobiletools.notes.pro.helpers | ||||||
|  |  | ||||||
| import android.graphics.Color | import android.graphics.Color | ||||||
|  | import org.joda.time.DateTime | ||||||
|  |  | ||||||
| const val NOTE_ID = "note_id" | const val NOTE_ID = "note_id" | ||||||
| const val OPEN_NOTE_ID = "open_note_id" | const val OPEN_NOTE_ID = "open_note_id" | ||||||
| @@ -39,6 +40,26 @@ const val FONT_SIZE_PERCENTAGE = "font_size_percentage" | |||||||
| const val EXPORT_MIME_TYPE = "text/plain" | const val EXPORT_MIME_TYPE = "text/plain" | ||||||
| const val ADD_NEW_CHECKLIST_ITEMS_TOP = "add_new_checklist_items_top" | const val ADD_NEW_CHECKLIST_ITEMS_TOP = "add_new_checklist_items_top" | ||||||
|  |  | ||||||
|  | // auto backups | ||||||
|  | const val AUTOMATIC_BACKUP_REQUEST_CODE = 10001 | ||||||
|  | const val AUTO_BACKUP_INTERVAL_IN_DAYS = 1 | ||||||
|  |  | ||||||
|  | // 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) | ||||||
|  | } | ||||||
|  |  | ||||||
| // gravity | // gravity | ||||||
| const val GRAVITY_START = 0 | const val GRAVITY_START = 0 | ||||||
| const val GRAVITY_CENTER = 1 | const val GRAVITY_CENTER = 1 | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import android.content.Context | |||||||
| import android.os.Handler | import android.os.Handler | ||||||
| import android.os.Looper | import android.os.Looper | ||||||
| import com.simplemobiletools.commons.activities.BaseSimpleActivity | import com.simplemobiletools.commons.activities.BaseSimpleActivity | ||||||
|  | import com.simplemobiletools.commons.helpers.ExportResult | ||||||
| import com.simplemobiletools.commons.helpers.PROTECTION_NONE | import com.simplemobiletools.commons.helpers.PROTECTION_NONE | ||||||
| import com.simplemobiletools.commons.helpers.ensureBackgroundThread | import com.simplemobiletools.commons.helpers.ensureBackgroundThread | ||||||
| import com.simplemobiletools.notes.pro.R | import com.simplemobiletools.notes.pro.R | ||||||
| @@ -11,7 +12,10 @@ import com.simplemobiletools.notes.pro.extensions.config | |||||||
| import com.simplemobiletools.notes.pro.extensions.notesDB | import com.simplemobiletools.notes.pro.extensions.notesDB | ||||||
| import com.simplemobiletools.notes.pro.models.Note | import com.simplemobiletools.notes.pro.models.Note | ||||||
| import com.simplemobiletools.notes.pro.models.NoteType | import com.simplemobiletools.notes.pro.models.NoteType | ||||||
|  | import kotlinx.serialization.encodeToString | ||||||
|  | import kotlinx.serialization.json.Json | ||||||
| import java.io.File | import java.io.File | ||||||
|  | import java.io.OutputStream | ||||||
|  |  | ||||||
| class NotesHelper(val context: Context) { | class NotesHelper(val context: Context) { | ||||||
|     fun getNotes(callback: (notes: List<Note>) -> Unit) { |     fun getNotes(callback: (notes: List<Note>) -> Unit) { | ||||||
| @@ -124,6 +128,18 @@ class NotesHelper(val context: Context) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun exportNotes(notesToBackup: List<Note>, outputStream: OutputStream): ExportResult { | ||||||
|  |         return try { | ||||||
|  |             val jsonString = Json.encodeToString(notesToBackup) | ||||||
|  |             outputStream.use { | ||||||
|  |                 it.write(jsonString.toByteArray()) | ||||||
|  |             } | ||||||
|  |             ExportResult.EXPORT_OK | ||||||
|  |         } catch (_: Error) { | ||||||
|  |             ExportResult.EXPORT_FAIL | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     enum class ImportResult { |     enum class ImportResult { | ||||||
|         IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL, IMPORT_NOTHING_NEW |         IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL, IMPORT_NOTHING_NEW | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -0,0 +1,17 @@ | |||||||
|  | package com.simplemobiletools.notes.pro.receivers | ||||||
|  |  | ||||||
|  | import android.content.BroadcastReceiver | ||||||
|  | import android.content.Context | ||||||
|  | import android.content.Intent | ||||||
|  | import android.os.PowerManager | ||||||
|  | import com.simplemobiletools.notes.pro.extensions.backupNotes | ||||||
|  |  | ||||||
|  | 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, "simplenotes:automaticbackupreceiver") | ||||||
|  |         wakelock.acquire(3000) | ||||||
|  |         context.backupNotes() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | package com.simplemobiletools.notes.pro.receivers | ||||||
|  |  | ||||||
|  | import android.content.BroadcastReceiver | ||||||
|  | import android.content.Context | ||||||
|  | import android.content.Intent | ||||||
|  | import com.simplemobiletools.commons.helpers.ensureBackgroundThread | ||||||
|  | import com.simplemobiletools.notes.pro.extensions.checkAndBackupNotesOnBoot | ||||||
|  |  | ||||||
|  | class BootCompletedReceiver : BroadcastReceiver() { | ||||||
|  |  | ||||||
|  |     override fun onReceive(context: Context, intent: Intent) { | ||||||
|  |         ensureBackgroundThread { | ||||||
|  |             context.apply { | ||||||
|  |                 checkAndBackupNotesOnBoot() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -386,6 +386,47 @@ | |||||||
|                 android:text="@string/import_notes" /> |                 android:text="@string/import_notes" /> | ||||||
|             </RelativeLayout> |             </RelativeLayout> | ||||||
|  |  | ||||||
|  |             <include | ||||||
|  |                 android:id="@+id/settings_migrating_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" /> | ||||||
							
								
								
									
										54
									
								
								app/src/main/res/layout/dialog_manage_automatic_backups.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								app/src/main/res/layout/dialog_manage_automatic_backups.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | <?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_notes_scrollview" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content"> | ||||||
|  |  | ||||||
|  |     <LinearLayout | ||||||
|  |         android:id="@+id/backup_notes_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_notes_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_notes_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_notes_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_json" | ||||||
|  |             app:endIconDrawable="@drawable/ic_info_vector" | ||||||
|  |             app:endIconMode="custom"> | ||||||
|  |  | ||||||
|  |             <com.google.android.material.textfield.TextInputEditText | ||||||
|  |                 android:id="@+id/backup_notes_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> | ||||||
|  |  | ||||||
|  |     </LinearLayout> | ||||||
|  | </ScrollView> | ||||||
		Reference in New Issue
	
	Block a user