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:
parent
82048f2754
commit
f5e9c30d17
4 changed files with 75 additions and 48 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -37,7 +37,7 @@ abstract class RequireProvisioningActivity : BackupActivity() {
|
|||
if (!getViewModel().validLocationIsSet()) {
|
||||
finishAfterTransition()
|
||||
}
|
||||
}
|
||||
} else getViewModel().onStorageLocationChanged()
|
||||
}
|
||||
|
||||
protected val isSetupWizard: Boolean
|
||||
|
|
|
@ -22,4 +22,8 @@ abstract class RequireProvisioningViewModel(
|
|||
|
||||
internal fun recoveryCodeIsSet() = keyManager.hasBackupKey()
|
||||
|
||||
open fun onStorageLocationChanged() {
|
||||
// noop can be overwritten by sub-classes
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue