From f5e9c30d1776a269747998a0cdf96524e47e585f Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 21 Oct 2020 14:23:18 -0300 Subject: [PATCH] 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. --- .../seedvault/settings/SettingsFragment.kt | 51 ++------------ .../seedvault/settings/SettingsViewModel.kt | 66 +++++++++++++++++++ .../ui/RequireProvisioningActivity.kt | 2 +- .../ui/RequireProvisioningViewModel.kt | 4 ++ 4 files changed, 75 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt index 6acf3476..7e469ada 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt @@ -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 - } - } - } diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt index d581945c..773b7f95 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt @@ -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(false) + val backupPossible: LiveData = mBackupPossible + internal val lastBackupTime = metadataManager.lastBackupTime private val mAppStatusList = switchMap(lastBackupTime) { @@ -67,6 +79,26 @@ internal class SettingsViewModel( private val mAppEditMode = MutableLiveData() internal val appEditMode: LiveData = mAppEditMode + private val storageObserver = object : ContentObserver(null) { + override fun onChange(selfChange: Boolean, uris: MutableCollection, 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() { diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningActivity.kt index f118aab3..c0a511df 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningActivity.kt @@ -37,7 +37,7 @@ abstract class RequireProvisioningActivity : BackupActivity() { if (!getViewModel().validLocationIsSet()) { finishAfterTransition() } - } + } else getViewModel().onStorageLocationChanged() } protected val isSetupWizard: Boolean diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt index 759e82b5..50952758 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt @@ -22,4 +22,8 @@ abstract class RequireProvisioningViewModel( internal fun recoveryCodeIsSet() = keyManager.hasBackupKey() + open fun onStorageLocationChanged() { + // noop can be overwritten by sub-classes + } + }