Disable automatic backups when a removable USB flash drive is used
as storage location. The backup backoff time is not reliable for this as the system still attempts to backup the magic @pm@ package without checking for the backoff value.
This commit is contained in:
parent
2c4d44c5b9
commit
26f23e95fe
8 changed files with 59 additions and 19 deletions
|
@ -19,6 +19,11 @@
|
||||||
android:name="android.permission.MANAGE_USB"
|
android:name="android.permission.MANAGE_USB"
|
||||||
tools:ignore="ProtectedPermissions" />
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
|
||||||
|
<!-- This is needed to change system backup settings -->
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_SECURE_SETTINGS"
|
||||||
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Backup"
|
android:name=".Backup"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
|
|
@ -50,7 +50,7 @@ class RestoreProgressFragment : Fragment() {
|
||||||
// success
|
// success
|
||||||
currentPackageView.text = getString(R.string.restore_finished_success)
|
currentPackageView.text = getString(R.string.restore_finished_success)
|
||||||
val settingsManager = (requireContext().applicationContext as Backup).settingsManager
|
val settingsManager = (requireContext().applicationContext as Backup).settingsManager
|
||||||
warningView.text = if (settingsManager.getStorage()?.ejectable == true) {
|
warningView.text = if (settingsManager.getStorage()?.isUsb == true) {
|
||||||
getString(R.string.restore_finished_warning_only_installed, getString(R.string.restore_finished_warning_ejectable))
|
getString(R.string.restore_finished_warning_only_installed, getString(R.string.restore_finished_warning_ejectable))
|
||||||
} else {
|
} else {
|
||||||
getString(R.string.restore_finished_warning_only_installed, null)
|
getString(R.string.restore_finished_warning_only_installed, null)
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.stevesoltys.backup.settings
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.provider.Settings
|
||||||
|
|
||||||
|
private val SETTING = Settings.Secure.BACKUP_MANAGER_CONSTANTS
|
||||||
|
private const val DELIMITER = ','
|
||||||
|
|
||||||
|
private const val KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS = "key_value_backup_interval_milliseconds"
|
||||||
|
private const val FULL_BACKUP_INTERVAL_MILLISECONDS = "full_backup_interval_milliseconds"
|
||||||
|
|
||||||
|
object BackupManagerSettings {
|
||||||
|
|
||||||
|
fun enableAutomaticBackups(resolver: ContentResolver) {
|
||||||
|
// setting this to null will cause the BackupManagerConstants to use default values
|
||||||
|
setSettingValue(resolver, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disableAutomaticBackups(resolver: ContentResolver) {
|
||||||
|
val value = Long.MAX_VALUE
|
||||||
|
val kv = "$KEY_VALUE_BACKUP_INTERVAL_MILLISECONDS=$value"
|
||||||
|
val full = "$FULL_BACKUP_INTERVAL_MILLISECONDS=$value"
|
||||||
|
setSettingValue(resolver, "$kv$DELIMITER$full")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setSettingValue(resolver: ContentResolver, value: String?) {
|
||||||
|
Settings.Secure.putString(resolver, SETTING, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import java.util.*
|
||||||
|
|
||||||
private const val PREF_KEY_STORAGE_URI = "storageUri"
|
private const val PREF_KEY_STORAGE_URI = "storageUri"
|
||||||
private const val PREF_KEY_STORAGE_NAME = "storageName"
|
private const val PREF_KEY_STORAGE_NAME = "storageName"
|
||||||
private const val PREF_KEY_STORAGE_EJECTABLE = "storageEjectable"
|
private const val PREF_KEY_STORAGE_IS_USB = "storageIsUsb"
|
||||||
|
|
||||||
private const val PREF_KEY_FLASH_DRIVE_NAME = "flashDriveName"
|
private const val PREF_KEY_FLASH_DRIVE_NAME = "flashDriveName"
|
||||||
private const val PREF_KEY_FLASH_DRIVE_SERIAL_NUMBER = "flashSerialNumber"
|
private const val PREF_KEY_FLASH_DRIVE_SERIAL_NUMBER = "flashSerialNumber"
|
||||||
|
@ -28,7 +28,7 @@ class SettingsManager(context: Context) {
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putString(PREF_KEY_STORAGE_URI, storage.uri.toString())
|
.putString(PREF_KEY_STORAGE_URI, storage.uri.toString())
|
||||||
.putString(PREF_KEY_STORAGE_NAME, storage.name)
|
.putString(PREF_KEY_STORAGE_NAME, storage.name)
|
||||||
.putBoolean(PREF_KEY_STORAGE_EJECTABLE, storage.ejectable)
|
.putBoolean(PREF_KEY_STORAGE_IS_USB, storage.isUsb)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ class SettingsManager(context: Context) {
|
||||||
val uriStr = prefs.getString(PREF_KEY_STORAGE_URI, null) ?: return null
|
val uriStr = prefs.getString(PREF_KEY_STORAGE_URI, null) ?: return null
|
||||||
val uri = Uri.parse(uriStr)
|
val uri = Uri.parse(uriStr)
|
||||||
val name = prefs.getString(PREF_KEY_STORAGE_NAME, null) ?: throw IllegalStateException()
|
val name = prefs.getString(PREF_KEY_STORAGE_NAME, null) ?: throw IllegalStateException()
|
||||||
val ejectable = prefs.getBoolean(PREF_KEY_STORAGE_EJECTABLE, false)
|
val isUsb = prefs.getBoolean(PREF_KEY_STORAGE_IS_USB, false)
|
||||||
return Storage(uri, name, ejectable)
|
return Storage(uri, name, isUsb)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setFlashDrive(usb: FlashDrive?) {
|
fun setFlashDrive(usb: FlashDrive?) {
|
||||||
|
@ -86,9 +86,9 @@ class SettingsManager(context: Context) {
|
||||||
/**
|
/**
|
||||||
* Sets the last backup time to "now".
|
* Sets the last backup time to "now".
|
||||||
*/
|
*/
|
||||||
fun saveNewBackupTime() {
|
fun saveNewBackupTime(millis: Long = Date().time) {
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putLong(PREF_KEY_BACKUP_TIME, Date().time)
|
.putLong(PREF_KEY_BACKUP_TIME, millis)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ class SettingsManager(context: Context) {
|
||||||
data class Storage(
|
data class Storage(
|
||||||
val uri: Uri,
|
val uri: Uri,
|
||||||
val name: String,
|
val name: String,
|
||||||
val ejectable: Boolean) {
|
val isUsb: Boolean) {
|
||||||
fun getDocumentFile(context: Context) = DocumentFile.fromTreeUri(context, uri)
|
fun getDocumentFile(context: Context) = DocumentFile.fromTreeUri(context, uri)
|
||||||
?: throw AssertionError("Should only happen on API < 21.")
|
?: throw AssertionError("Should only happen on API < 21.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,7 +199,7 @@ class BackupCoordinator(
|
||||||
// back off if there's no storage set
|
// back off if there's no storage set
|
||||||
val storage = settingsManager.getStorage() ?: return defaultBackoff
|
val storage = settingsManager.getStorage() ?: return defaultBackoff
|
||||||
// don't back off if storage is not ejectable or available right now
|
// don't back off if storage is not ejectable or available right now
|
||||||
return if (!storage.ejectable || storage.getDocumentFile(context).isDirectory) noBackoff
|
return if (!storage.isUsb || storage.getDocumentFile(context).isDirectory) noBackoff
|
||||||
// otherwise back off
|
// otherwise back off
|
||||||
else defaultBackoff
|
else defaultBackoff
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ data class StorageRoot(
|
||||||
internal val title: String,
|
internal val title: String,
|
||||||
internal val summary: String?,
|
internal val summary: String?,
|
||||||
internal val availableBytes: Long?,
|
internal val availableBytes: Long?,
|
||||||
internal val supportsEject: Boolean,
|
internal val isUsb: Boolean,
|
||||||
internal val enabled: Boolean = true) {
|
internal val enabled: Boolean = true) {
|
||||||
|
|
||||||
internal val uri: Uri by lazy {
|
internal val uri: Uri by lazy {
|
||||||
|
@ -41,7 +41,7 @@ data class StorageRoot(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isInternal(): Boolean {
|
fun isInternal(): Boolean {
|
||||||
return authority == AUTHORITY_STORAGE && !supportsEject
|
return authority == AUTHORITY_STORAGE && !isUsb
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +122,6 @@ internal class StorageRootFetcher(private val context: Context) {
|
||||||
if (!supportsCreate || !supportsIsChild) return null
|
if (!supportsCreate || !supportsIsChild) return null
|
||||||
val rootId = cursor.getString(COLUMN_ROOT_ID)!!
|
val rootId = cursor.getString(COLUMN_ROOT_ID)!!
|
||||||
if (authority == AUTHORITY_STORAGE && rootId == ROOT_ID_HOME) return null
|
if (authority == AUTHORITY_STORAGE && rootId == ROOT_ID_HOME) return null
|
||||||
val supportsEject = flags and FLAG_SUPPORTS_EJECT != 0
|
|
||||||
return StorageRoot(
|
return StorageRoot(
|
||||||
authority = authority,
|
authority = authority,
|
||||||
rootId = rootId,
|
rootId = rootId,
|
||||||
|
@ -131,13 +130,13 @@ internal class StorageRootFetcher(private val context: Context) {
|
||||||
title = cursor.getString(COLUMN_TITLE)!!,
|
title = cursor.getString(COLUMN_TITLE)!!,
|
||||||
summary = cursor.getString(COLUMN_SUMMARY),
|
summary = cursor.getString(COLUMN_SUMMARY),
|
||||||
availableBytes = cursor.getLong(COLUMN_AVAILABLE_BYTES),
|
availableBytes = cursor.getLong(COLUMN_AVAILABLE_BYTES),
|
||||||
supportsEject = supportsEject
|
isUsb = flags and FLAG_REMOVABLE_USB != 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkOrAddUsbRoot(roots: ArrayList<StorageRoot>) {
|
private fun checkOrAddUsbRoot(roots: ArrayList<StorageRoot>) {
|
||||||
for (root in roots) {
|
for (root in roots) {
|
||||||
if (root.authority == AUTHORITY_STORAGE && root.supportsEject) return
|
if (root.authority == AUTHORITY_STORAGE && root.isUsb) return
|
||||||
}
|
}
|
||||||
val root = StorageRoot(
|
val root = StorageRoot(
|
||||||
authority = AUTHORITY_STORAGE,
|
authority = AUTHORITY_STORAGE,
|
||||||
|
@ -147,7 +146,7 @@ internal class StorageRootFetcher(private val context: Context) {
|
||||||
title = context.getString(R.string.storage_fake_drive_title),
|
title = context.getString(R.string.storage_fake_drive_title),
|
||||||
summary = context.getString(R.string.storage_fake_drive_summary),
|
summary = context.getString(R.string.storage_fake_drive_summary),
|
||||||
availableBytes = null,
|
availableBytes = null,
|
||||||
supportsEject = true,
|
isUsb = true,
|
||||||
enabled = false
|
enabled = false
|
||||||
)
|
)
|
||||||
roots.add(root)
|
roots.add(root)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import com.stevesoltys.backup.Backup
|
import com.stevesoltys.backup.Backup
|
||||||
import com.stevesoltys.backup.R
|
import com.stevesoltys.backup.R
|
||||||
import com.stevesoltys.backup.isMassStorage
|
import com.stevesoltys.backup.isMassStorage
|
||||||
|
import com.stevesoltys.backup.settings.BackupManagerSettings
|
||||||
import com.stevesoltys.backup.settings.FlashDrive
|
import com.stevesoltys.backup.settings.FlashDrive
|
||||||
import com.stevesoltys.backup.settings.Storage
|
import com.stevesoltys.backup.settings.Storage
|
||||||
import com.stevesoltys.backup.transport.ConfigurableBackupTransportService
|
import com.stevesoltys.backup.transport.ConfigurableBackupTransportService
|
||||||
|
@ -45,7 +46,7 @@ internal abstract class StorageViewModel(private val app: Application) : Android
|
||||||
internal fun validLocationIsSet(context: Context): Boolean {
|
internal fun validLocationIsSet(context: Context): Boolean {
|
||||||
val settingsManager = (context.applicationContext as Backup).settingsManager
|
val settingsManager = (context.applicationContext as Backup).settingsManager
|
||||||
val storage = settingsManager.getStorage() ?: return false
|
val storage = settingsManager.getStorage() ?: return false
|
||||||
if (storage.ejectable) return true
|
if (storage.isUsb) return true
|
||||||
return storage.getDocumentFile(context).isDirectory
|
return storage.getDocumentFile(context).isDirectory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,16 +89,20 @@ internal abstract class StorageViewModel(private val app: Application) : Android
|
||||||
} else {
|
} else {
|
||||||
root.title
|
root.title
|
||||||
}
|
}
|
||||||
val storage = Storage(uri, name, root.supportsEject)
|
val storage = Storage(uri, name, root.isUsb)
|
||||||
settingsManager.setStorage(storage)
|
settingsManager.setStorage(storage)
|
||||||
|
|
||||||
// reset time of last backup to "Never"
|
// reset time of last backup to "Never"
|
||||||
settingsManager.resetBackupTime()
|
settingsManager.resetBackupTime()
|
||||||
|
|
||||||
if (storage.ejectable) {
|
if (storage.isUsb) {
|
||||||
val wasSaved = saveUsbDevice()
|
val wasSaved = saveUsbDevice()
|
||||||
// reset stored flash drive, if we did not update it
|
// reset stored flash drive, if we did not update it
|
||||||
if (!wasSaved) settingsManager.setFlashDrive(null)
|
if (!wasSaved) settingsManager.setFlashDrive(null)
|
||||||
|
BackupManagerSettings.disableAutomaticBackups(app.contentResolver)
|
||||||
|
} else {
|
||||||
|
settingsManager.setFlashDrive(null)
|
||||||
|
BackupManagerSettings.enableAutomaticBackups(app.contentResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop backup service to be sure the old location will get updated
|
// stop backup service to be sure the old location will get updated
|
||||||
|
@ -114,7 +119,7 @@ internal abstract class StorageViewModel(private val app: Application) : Android
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.w(TAG, "No USB device found for ejectable storage.")
|
Log.e(TAG, "No USB device found even though we were expecting one.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,5 +3,6 @@
|
||||||
<privapp-permissions package="com.stevesoltys.backup">
|
<privapp-permissions package="com.stevesoltys.backup">
|
||||||
<permission name="android.permission.BACKUP"/>
|
<permission name="android.permission.BACKUP"/>
|
||||||
<permission name="android.permission.MANAGE_USB"/>
|
<permission name="android.permission.MANAGE_USB"/>
|
||||||
|
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
|
||||||
</privapp-permissions>
|
</privapp-permissions>
|
||||||
</permissions>
|
</permissions>
|
Loading…
Reference in a new issue