From 111c1fd8e09f7a6aba2993b596abc645af6d2231 Mon Sep 17 00:00:00 2001 From: Matt Schlosser Date: Fri, 10 Jul 2020 20:08:44 -0600 Subject: [PATCH 1/3] Minor backup tweaks Users on all Android versions are now asked where they want to save the file when picking the phone storage option from the backup system When backing up modules, users now have an option to choose phone storage WHen a backup is completed, the location no longer shows Removed unnecessary WRITE_EXTERNAL_STORAGE permissions since it is no longer being used to directly write to external storage. Closes #756 --- app/src/main/AndroidManifest.xml | 1 - .../android/control/backup/BackupControl.kt | 82 +++++++++++++++---- .../view/activity/page/MainBibleActivity.kt | 6 ++ .../view/activity/page/MenuCommandHandler.kt | 44 ++-------- app/src/main/res/values/strings.xml | 3 +- 5 files changed, 80 insertions(+), 56 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2031ff0c3e..b5b4a47274 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,7 +7,6 @@ android:versionName="3.3.386"> - + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/zip" + putExtra(Intent.EXTRA_TITLE, fileName) + } + callingActivity.startActivityForResult(intent, MainBibleActivity.REQUEST_PICK_FILE_FOR_BACKUP_MODULES) + } + .setPositiveButton(callingActivity.getString(R.string.backup_share)) { dialog, which -> +// backupDatabaseViaSendIntent(callingActivity) + val modulesString = books.joinToString(", ") { it.abbreviation } + val subject = BibleApplication.application.getString(R.string.backup_modules_email_subject_2, CommonUtils.applicationNameMedium) + val message = BibleApplication.application.getString(R.string.backup_modules_email_message_2, CommonUtils.applicationNameMedium, modulesString) + + val uri = FileProvider.getUriForFile(callingActivity, BuildConfig.APPLICATION_ID + ".provider", zipFile) + val email = Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_STREAM, uri) + putExtra(Intent.EXTRA_SUBJECT, subject) + putExtra(Intent.EXTRA_TEXT, message) + type = "application/zip" + } + val chooserIntent = Intent.createChooser(email, getString(R.string.send_backup_file)) + chooserIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + callingActivity.startActivity(chooserIntent) + } + + .setNeutralButton(callingActivity.getString(R.string.cancel), null) + .show() - val uri = FileProvider.getUriForFile(callingActivity, BuildConfig.APPLICATION_ID + ".provider", zipFile) - val email = Intent(Intent.ACTION_SEND).apply { - putExtra(Intent.EXTRA_STREAM, uri) - putExtra(Intent.EXTRA_SUBJECT, subject) - putExtra(Intent.EXTRA_TEXT, message) - type = "application/zip" - } - val chooserIntent = Intent.createChooser(email, getString(R.string.send_backup_file)) - chooserIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - callingActivity.startActivity(chooserIntent) } /** restore database from sd card @@ -334,7 +380,7 @@ class BackupControl @Inject constructor() { // this is now unused because And Bible databases are held on the SD card to facilitate easier backup by file copy private lateinit var internalDbDir : File; private lateinit var internalDbBackupDir: File; - + private const val MODULE_BACKUP_NAME = "modules.zip" fun setupDirs(context: Context) { internalDbDir = File(context.getDatabasePath(DATABASE_NAME).parent!!) internalDbBackupDir = File(context.filesDir, "/backup") diff --git a/app/src/main/java/net/bible/android/view/activity/page/MainBibleActivity.kt b/app/src/main/java/net/bible/android/view/activity/page/MainBibleActivity.kt index 76c47055ed..47c6dd1995 100644 --- a/app/src/main/java/net/bible/android/view/activity/page/MainBibleActivity.kt +++ b/app/src/main/java/net/bible/android/view/activity/page/MainBibleActivity.kt @@ -1158,6 +1158,11 @@ class MainBibleActivity : CustomTitlebarActivityBase(), VerseActionModeMediator. backupControl.backupDatabaseToUri(data!!.data!!) } } + REQUEST_PICK_FILE_FOR_BACKUP_MODULES -> { + GlobalScope.launch(Dispatchers.IO) { + backupControl.backupModulesToUri(data!!.data!!) + } + } WORKSPACE_CHANGED -> { val extras = data?.extras val workspaceId = extras?.getLong("workspaceId") @@ -1441,6 +1446,7 @@ class MainBibleActivity : CustomTitlebarActivityBase(), VerseActionModeMediator. const val COLORS_CHANGED = 5 const val WORKSPACE_CHANGED = 6 const val REQUEST_PICK_FILE_FOR_BACKUP_DB = 7 + const val REQUEST_PICK_FILE_FOR_BACKUP_MODULES = 8 private const val SCREEN_KEEP_ON_PREF = "screen_keep_on_pref" diff --git a/app/src/main/java/net/bible/android/view/activity/page/MenuCommandHandler.kt b/app/src/main/java/net/bible/android/view/activity/page/MenuCommandHandler.kt index ea66f185cc..56cae7b53d 100644 --- a/app/src/main/java/net/bible/android/view/activity/page/MenuCommandHandler.kt +++ b/app/src/main/java/net/bible/android/view/activity/page/MenuCommandHandler.kt @@ -220,20 +220,12 @@ constructor(private val callingActivity: MainBibleActivity, .setTitle(callingActivity.getString(R.string.backup_backup_title)) .setMessage(callingActivity.getString(R.string.backup_backup_message)) .setNegativeButton(callingActivity.getString(R.string.backup_phone_storage)) { dialog, which -> - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - if (ContextCompat.checkSelfPermission(callingActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { - ActivityCompat.requestPermissions(callingActivity, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), BACKUP_SAVE_REQUEST) - } else { - backupControl.backupDatabase() - } - } else { - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "application/x-sqlite3" - putExtra(Intent.EXTRA_TITLE, DATABASE_NAME) - } - callingActivity.startActivityForResult(intent, REQUEST_PICK_FILE_FOR_BACKUP_DB) + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/x-sqlite3" + putExtra(Intent.EXTRA_TITLE, DATABASE_NAME) } + callingActivity.startActivityForResult(intent, REQUEST_PICK_FILE_FOR_BACKUP_DB) } .setPositiveButton(callingActivity.getString(R.string.backup_share)) { dialog, which -> backupControl.backupDatabaseViaSendIntent(callingActivity) @@ -259,29 +251,9 @@ constructor(private val callingActivity: MainBibleActivity, requestCode = IntentHelper.UPDATE_SUGGESTED_DOCUMENTS_ON_FINISH } R.id.restore_app_database -> { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - AlertDialog.Builder(callingActivity) - .setTitle(callingActivity.getString(R.string.backup_restore_title)) - .setMessage(callingActivity.getString(R.string.backup_restore_message)) - .setNegativeButton(callingActivity.getString(R.string.backup_phone_storage)) { dialog, which -> - if (ContextCompat.checkSelfPermission(callingActivity, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { - ActivityCompat.requestPermissions(callingActivity, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), BACKUP_RESTORE_REQUEST) - } else { - backupControl.restoreDatabase() - } - } - .setPositiveButton(callingActivity.getString(R.string.backup_manually)) { dialog, which -> - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.type = "application/*" - callingActivity.startActivityForResult(intent, REQUEST_PICK_FILE_FOR_BACKUP_RESTORE) - } - .setNeutralButton(callingActivity.getString(R.string.cancel), null) - .show() - } else { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.type = "application/*" - callingActivity.startActivityForResult(intent, REQUEST_PICK_FILE_FOR_BACKUP_RESTORE) - } + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.type = "application/*" + callingActivity.startActivityForResult(intent, REQUEST_PICK_FILE_FOR_BACKUP_RESTORE) isHandled = true } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ada54c9ed6..c70d46b789 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -481,7 +481,8 @@ Overwrite My Notes, Bookmarks, Reading Plans and Workspaces? My Notes, Bookmarks, Reading Plans and Workspaces restored successfully Loading database from backup… - My Notes, Bookmarks, Reading Plans and Workspaces backed up successfully to %s. + My Notes, Bookmarks, Reading Plans and Workspaces backed up successfully. + Selected modules backed up successfully. Backup file was not found %s modules backup From df7bcbc65fa54988c8d192f137d0b932f4e15cdf Mon Sep 17 00:00:00 2001 From: Matt Schlosser Date: Sat, 11 Jul 2020 08:51:13 -0600 Subject: [PATCH 2/3] Remove unneeded backup code --- .../java/net/bible/android/SharedConstants.kt | 6 --- .../android/control/backup/BackupControl.kt | 43 ------------------- .../view/activity/page/MainBibleActivity.kt | 12 ------ .../view/activity/page/MenuCommandHandler.kt | 2 - 4 files changed, 63 deletions(-) diff --git a/app/src/main/java/net/bible/android/SharedConstants.kt b/app/src/main/java/net/bible/android/SharedConstants.kt index db202c35f1..2cd5f95161 100644 --- a/app/src/main/java/net/bible/android/SharedConstants.kt +++ b/app/src/main/java/net/bible/android/SharedConstants.kt @@ -31,7 +31,6 @@ object SharedConstants { const val REQUIRED_MEGS_FOR_DOWNLOADS: Long = 50 const val NO_VALUE = -1 - private const val BACKUP_SUBDIR_NAME = "andbible_backup" private const val CSS_SUBDIR_NAME = "css" private const val MANUAL_INSTALL_SUBDIR = "jsword" @@ -62,9 +61,4 @@ object SharedConstants { return File(sdcard, MANUAL_INSTALL_SUBDIR) } - val backupDir: File - get() { - val sdcard = Environment.getExternalStorageDirectory() - return File(sdcard, BACKUP_SUBDIR_NAME) - } } diff --git a/app/src/main/java/net/bible/android/control/backup/BackupControl.kt b/app/src/main/java/net/bible/android/control/backup/BackupControl.kt index 9929fb5c61..d614aae099 100644 --- a/app/src/main/java/net/bible/android/control/backup/BackupControl.kt +++ b/app/src/main/java/net/bible/android/control/backup/BackupControl.kt @@ -74,27 +74,6 @@ import kotlin.coroutines.resume @ApplicationScope class BackupControl @Inject constructor() { - /** return true if a backup has been done and the file is on the sd card. - */ - private val isBackupFileExists: Boolean - get() = File(SharedConstants.backupDir, DATABASE_NAME).exists() - - /** backup database to sd card - */ - fun backupDatabase() { - mainBibleActivity.windowRepository.saveIntoDb() - db.sync() - val ok = FileManager.copyFile(DATABASE_NAME, internalDbDir, SharedConstants.backupDir) - - if (ok) { - Log.d(TAG, "Copied database to internal memory successfully") - Dialogs.instance.showMsg(R.string.backup_success, SharedConstants.backupDir.absolutePath) - } else { - Log.e(TAG, "Error copying database to internal memory") - Dialogs.instance.showErrorMsg(R.string.error_occurred) - } - } - /** Backup database to Uri returned from ACTION_CREATE_DOCUMENT intent */ fun backupDatabaseToUri( uri: Uri) : Boolean { @@ -352,28 +331,6 @@ class BackupControl @Inject constructor() { } - /** restore database from sd card - */ - fun restoreDatabase() { - if (!isBackupFileExists) { - Dialogs.instance.showErrorMsg(R.string.error_no_backup_file) - } else { - Dialogs.instance.showMsg(R.string.restore_confirmation, true) { - BibleApplication.application.deleteDatabase(DATABASE_NAME) - val ok = FileManager.copyFile(DATABASE_NAME, SharedConstants.backupDir, internalDbDir) - - if (ok) { - DatabaseContainer.reset() - ABEventBus.getDefault().post(SynchronizeWindowsEvent(true)) - Log.d(TAG, "Copied database from internal memory successfully") - Dialogs.instance.showMsg(R.string.restore_success, SharedConstants.backupDir.name) - } else { - Log.e(TAG, "Error copying database from internal memory") - Dialogs.instance.showErrorMsg(R.string.error_occurred) - } - } - } - } companion object { diff --git a/app/src/main/java/net/bible/android/view/activity/page/MainBibleActivity.kt b/app/src/main/java/net/bible/android/view/activity/page/MainBibleActivity.kt index 47c6dd1995..4bfabda215 100644 --- a/app/src/main/java/net/bible/android/view/activity/page/MainBibleActivity.kt +++ b/app/src/main/java/net/bible/android/view/activity/page/MainBibleActivity.kt @@ -1300,16 +1300,6 @@ class MainBibleActivity : CustomTitlebarActivityBase(), VerseActionModeMediator. } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { when (requestCode) { - BACKUP_SAVE_REQUEST -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - backupControl.backupDatabase() - } else { - Dialogs.instance.showMsg(R.string.error_occurred) - } - BACKUP_RESTORE_REQUEST -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - backupControl.restoreDatabase() - } else { - Dialogs.instance.showMsg(R.string.error_occurred) - } SDCARD_READ_REQUEST -> if (grantResults.isNotEmpty()) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { documentControl.enableManualInstallFolder() @@ -1436,8 +1426,6 @@ class MainBibleActivity : CustomTitlebarActivityBase(), VerseActionModeMediator. companion object { lateinit var mainBibleActivity: MainBibleActivity - internal const val BACKUP_SAVE_REQUEST = 0 - internal const val BACKUP_RESTORE_REQUEST = 1 var initialized = false private const val SDCARD_READ_REQUEST = 2 diff --git a/app/src/main/java/net/bible/android/view/activity/page/MenuCommandHandler.kt b/app/src/main/java/net/bible/android/view/activity/page/MenuCommandHandler.kt index 56cae7b53d..8171d4ed0e 100644 --- a/app/src/main/java/net/bible/android/view/activity/page/MenuCommandHandler.kt +++ b/app/src/main/java/net/bible/android/view/activity/page/MenuCommandHandler.kt @@ -58,8 +58,6 @@ import net.bible.android.view.activity.installzip.InstallZip import net.bible.android.view.activity.mynote.MyNotes import net.bible.android.view.activity.navigation.ChooseDocument import net.bible.android.view.activity.navigation.History -import net.bible.android.view.activity.page.MainBibleActivity.Companion.BACKUP_RESTORE_REQUEST -import net.bible.android.view.activity.page.MainBibleActivity.Companion.BACKUP_SAVE_REQUEST import net.bible.android.view.activity.page.MainBibleActivity.Companion.REQUEST_PICK_FILE_FOR_BACKUP_DB import net.bible.android.view.activity.page.MainBibleActivity.Companion.REQUEST_PICK_FILE_FOR_BACKUP_RESTORE import net.bible.android.view.activity.readingplan.DailyReading From 4fbc76e7f2ae35f1e5e6c5b8e734e20c0934ba04 Mon Sep 17 00:00:00 2001 From: Matt Schlosser Date: Wed, 15 Jul 2020 22:03:10 -0600 Subject: [PATCH 3/3] Show success/error messages on main thread --- .../android/control/backup/BackupControl.kt | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/net/bible/android/control/backup/BackupControl.kt b/app/src/main/java/net/bible/android/control/backup/BackupControl.kt index d614aae099..27e3e208e1 100644 --- a/app/src/main/java/net/bible/android/control/backup/BackupControl.kt +++ b/app/src/main/java/net/bible/android/control/backup/BackupControl.kt @@ -76,7 +76,7 @@ class BackupControl @Inject constructor() { /** Backup database to Uri returned from ACTION_CREATE_DOCUMENT intent */ - fun backupDatabaseToUri( uri: Uri) : Boolean { + suspend fun backupDatabaseToUri( uri: Uri) { val out = BibleApplication.application.contentResolver.openOutputStream(uri)!! val filename = DATABASE_NAME; val f = File(internalDbDir, filename); @@ -91,15 +91,15 @@ class BackupControl @Inject constructor() { ok = false } - if (ok) { - Log.d(TAG, "Copied database to chosen backup location successfully") - Dialogs.instance.showMsg(R.string.backup_success) - } else { - Log.e(TAG, "Error copying database to chosen location.") - Dialogs.instance.showErrorMsg(R.string.error_occurred) + withContext(Dispatchers.Main) { + if (ok) { + Log.d(TAG, "Copied database to chosen backup location successfully") + Dialogs.instance.showMsg(R.string.backup_success) + } else { + Log.e(TAG, "Error copying database to chosen location.") + Dialogs.instance.showErrorMsg(R.string.error_occurred) + } } - - return ok; } /** backup database to custom target (email, drive etc.) via ACTION_SEND intent @@ -132,7 +132,7 @@ class BackupControl @Inject constructor() { /** restore database from custom source */ - fun restoreDatabaseViaIntent(inputStream: InputStream): Boolean { + suspend fun restoreDatabaseViaIntent(inputStream: InputStream): Boolean { val fileName = DATABASE_NAME internalDbBackupDir.mkdirs() val f = File(internalDbBackupDir, fileName) @@ -153,14 +153,15 @@ class BackupControl @Inject constructor() { } sqlDb.close() } - - if (ok) { - ABEventBus.getDefault().post(SynchronizeWindowsEvent(true)) - Log.d(TAG, "Restored database successfully") - Dialogs.instance.showMsg(R.string.restore_success) - } else { - Log.e(TAG, "Error restoring database") - Dialogs.instance.showErrorMsg(R.string.restore_unsuccessfull) + withContext(Dispatchers.Main) { + if (ok) { + ABEventBus.getDefault().post(SynchronizeWindowsEvent(true)) + Log.d(TAG, "Restored database successfully") + Dialogs.instance.showMsg(R.string.restore_success) + } else { + Log.e(TAG, "Error restoring database") + Dialogs.instance.showErrorMsg(R.string.restore_unsuccessfull) + } } f.delete() return ok @@ -259,7 +260,7 @@ class BackupControl @Inject constructor() { internalDbBackupDir.deleteRecursively() } - fun backupModulesToUri(uri: Uri) : Boolean { + suspend fun backupModulesToUri(uri: Uri) { // at this point the zip file has already been created val fileName = MODULE_BACKUP_NAME val zipFile = File(internalDbBackupDir, fileName) @@ -272,16 +273,15 @@ class BackupControl @Inject constructor() { } catch (ex: IOException) { ok = false } - - if (ok) { - Log.d(TAG, "Copied modules to chosen backup location successfully") - Dialogs.instance.showMsg(R.string.backup_modules_success) - } else { - Log.e(TAG, "Error copying modules to chosen location.") - Dialogs.instance.showErrorMsg(R.string.error_occurred) + withContext(Dispatchers.Main) { + if (ok) { + Log.d(TAG, "Copied modules to chosen backup location successfully") + Dialogs.instance.showMsg(R.string.backup_modules_success) + } else { + Log.e(TAG, "Error copying modules to chosen location.") + Dialogs.instance.showErrorMsg(R.string.error_occurred) + } } - - return ok; } suspend fun backupModulesViaIntent(callingActivity: Activity) = withContext(Dispatchers.Main) { @@ -309,7 +309,6 @@ class BackupControl @Inject constructor() { callingActivity.startActivityForResult(intent, MainBibleActivity.REQUEST_PICK_FILE_FOR_BACKUP_MODULES) } .setPositiveButton(callingActivity.getString(R.string.backup_share)) { dialog, which -> -// backupDatabaseViaSendIntent(callingActivity) val modulesString = books.joinToString(", ") { it.abbreviation } val subject = BibleApplication.application.getString(R.string.backup_modules_email_subject_2, CommonUtils.applicationNameMedium) val message = BibleApplication.application.getString(R.string.backup_modules_email_message_2, CommonUtils.applicationNameMedium, modulesString)