Also check internet access when determining whether backup storage is available

This commit also refactors the code in SettingsFragment and moves it into the SettingsViewModel.
The UsbMonitor turned out not to be reliable in determining changes to USB storage, so it was replaced with a ContentObserver which works for other storage types as well.
This commit is contained in:
Torsten Grote 2020-10-21 14:23:18 -03:00
parent 82048f2754
commit f5e9c30d17
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
4 changed files with 75 additions and 48 deletions

View file

@ -1,13 +1,8 @@
package com.stevesoltys.seedvault.settings
import android.app.backup.IBackupManager
import android.content.Context
import android.content.Context.BACKUP_SERVICE // ktlint-disable no-unused-imports
import android.content.Intent
import android.content.IntentFilter
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager.ACTION_USB_DEVICE_ATTACHED
import android.hardware.usb.UsbManager.ACTION_USB_DEVICE_DETACHED
import android.os.Bundle
import android.os.RemoteException
import android.provider.Settings
@ -19,20 +14,14 @@ import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.Preference.OnPreferenceChangeListener
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.TwoStatePreference
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.UsbMonitor
import com.stevesoltys.seedvault.isMassStorage
import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.restore.RestoreActivity
import com.stevesoltys.seedvault.ui.toRelativeTime
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
@ -54,22 +43,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
private var menuRestore: MenuItem? = null
private var storage: Storage? = null
private val usbFilter = IntentFilter(ACTION_USB_DEVICE_ATTACHED).apply {
addAction(ACTION_USB_DEVICE_DETACHED)
}
private val usbReceiver = object : UsbMonitor() {
override fun shouldMonitorStatus(
context: Context,
action: String,
device: UsbDevice
): Boolean {
return device.isMassStorage()
}
override fun onStatusChanged(context: Context, action: String, device: UsbDevice) {
lifecycleScope.launch { setMenuItemStates() }
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
permitDiskReads {
@ -149,15 +122,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
setBackupEnabledState()
setBackupLocationSummary()
setAutoRestoreState()
lifecycleScope.launch { setMenuItemStates() }
// TODO we should also monitor network changes, if storage requires network
if (storage?.isUsb == true) context?.registerReceiver(usbReceiver, usbFilter)
}
override fun onStop() {
super.onStop()
if (storage?.isUsb == true) context?.unregisterReceiver(usbReceiver)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -168,7 +132,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
if (resources.getBoolean(R.bool.show_restore_in_settings)) {
menuRestore?.isVisible = true
}
lifecycleScope.launch { setMenuItemStates() }
viewModel.backupPossible.observe(viewLifecycleOwner, Observer { possible ->
menuBackupNow?.isEnabled = possible
menuRestore?.isEnabled = possible
})
}
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
@ -223,14 +190,4 @@ class SettingsFragment : PreferenceFragmentCompat() {
backupStatus.summary = getString(R.string.settings_backup_status_summary, lastBackup)
}
private suspend fun setMenuItemStates() {
if (menuBackupNow != null && menuRestore != null) {
val enabled = withContext(Dispatchers.IO) {
settingsManager.canDoBackupNow()
}
menuBackupNow?.isEnabled = enabled
menuRestore?.isEnabled = enabled
}
}
}

View file

@ -2,6 +2,12 @@ package com.stevesoltys.seedvault.settings
import android.app.Application
import android.content.pm.PackageManager.NameNotFoundException
import android.database.ContentObserver
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.net.Uri
import android.provider.Settings
import android.util.Log
import android.widget.Toast
@ -54,8 +60,14 @@ internal class SettingsViewModel(
private val packageService: PackageService
) : RequireProvisioningViewModel(app, settingsManager, keyManager) {
private val contentResolver = app.contentResolver
private val connectivityManager = app.getSystemService(ConnectivityManager::class.java)
override val isRestoreOperation = false
private val mBackupPossible = MutableLiveData<Boolean>(false)
val backupPossible: LiveData<Boolean> = mBackupPossible
internal val lastBackupTime = metadataManager.lastBackupTime
private val mAppStatusList = switchMap(lastBackupTime) {
@ -67,6 +79,26 @@ internal class SettingsViewModel(
private val mAppEditMode = MutableLiveData<Boolean>()
internal val appEditMode: LiveData<Boolean> = mAppEditMode
private val storageObserver = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean, uris: MutableCollection<Uri>, flags: Int) {
onStorageLocationChanged()
}
}
private inner class NetworkObserver : ConnectivityManager.NetworkCallback() {
var registered = false
override fun onAvailable(network: Network) {
onStorageLocationChanged()
}
override fun onLost(network: Network) {
super.onLost(network)
onStorageLocationChanged()
}
}
private val networkCallback = NetworkObserver()
init {
val scope = permitDiskReads {
// this shouldn't cause disk reads, but it still does
@ -76,6 +108,40 @@ internal class SettingsViewModel(
// ensures the lastBackupTime LiveData gets set
metadataManager.getLastBackupTime()
}
onStorageLocationChanged()
}
override fun onStorageLocationChanged() {
val storage = settingsManager.getStorage() ?: return
// register storage observer
contentResolver.unregisterContentObserver(storageObserver)
contentResolver.registerContentObserver(storage.uri, false, storageObserver)
// register network observer if needed
if (networkCallback.registered && !storage.requiresNetwork) {
connectivityManager.unregisterNetworkCallback(networkCallback)
networkCallback.registered = false
} else if (!networkCallback.registered && storage.requiresNetwork) {
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, networkCallback)
networkCallback.registered = true
}
viewModelScope.launch(Dispatchers.IO) {
val canDo = settingsManager.canDoBackupNow()
mBackupPossible.postValue(canDo)
}
}
override fun onCleared() {
contentResolver.unregisterContentObserver(storageObserver)
if (networkCallback.registered) {
connectivityManager.unregisterNetworkCallback(networkCallback)
networkCallback.registered = false
}
}
internal fun backupNow() {

View file

@ -37,7 +37,7 @@ abstract class RequireProvisioningActivity : BackupActivity() {
if (!getViewModel().validLocationIsSet()) {
finishAfterTransition()
}
}
} else getViewModel().onStorageLocationChanged()
}
protected val isSetupWizard: Boolean

View file

@ -22,4 +22,8 @@ abstract class RequireProvisioningViewModel(
internal fun recoveryCodeIsSet() = keyManager.hasBackupKey()
open fun onStorageLocationChanged() {
// noop can be overwritten by sub-classes
}
}