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:
Torsten Grote 2019-09-19 16:29:53 -03:00
parent 2c4d44c5b9
commit 26f23e95fe
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
8 changed files with 59 additions and 19 deletions

View file

@ -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"

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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.")
} }

View file

@ -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
} }

View file

@ -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)

View file

@ -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
} }

View file

@ -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>