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