Check also availability of internet access when using online storage

This moves these availability checks into the Storage class, so they can be used in various places without duplicating code.
This commit is contained in:
Torsten Grote 2020-10-20 11:49:17 -03:00
parent 0a2131e108
commit 141fe7575d
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
6 changed files with 46 additions and 35 deletions

View file

@ -151,6 +151,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
setAutoRestoreState()
lifecycleScope.launch { setMenuItemStates() }
// TODO we should also monitor network changes, if storage requires network
if (storage?.isUsb == true) context?.registerReceiver(usbReceiver, usbFilter)
}
@ -233,7 +234,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private suspend fun storageAvailable(storage: Storage) = withContext(Dispatchers.IO) {
val context = context ?: return@withContext false
(!storage.isUsb || storage.getDocumentFile(context).isDirectory)
!storage.isUnavailableUsb(context) && !storage.isUnavailableNetwork(context)
}
}

View file

@ -2,8 +2,11 @@ package com.stevesoltys.seedvault.settings
import android.content.Context
import android.hardware.usb.UsbDevice
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.Uri
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import com.stevesoltys.seedvault.permitDiskReads
@ -127,6 +130,30 @@ data class Storage(
) {
fun getDocumentFile(context: Context) = DocumentFile.fromTreeUri(context, uri)
?: throw AssertionError("Should only happen on API < 21.")
/**
* Returns true if this is USB storage that is not available, false otherwise.
*
* Must be run off UI thread (ideally I/O).
*/
@WorkerThread
fun isUnavailableUsb(context: Context): Boolean {
return isUsb && !getDocumentFile(context).isDirectory
}
/**
* Returns true if this is storage that requires network access,
* but it isn't available right now.
*/
fun isUnavailableNetwork(context: Context): Boolean {
return requiresNetwork && !hasInternet(context)
}
private fun hasInternet(context: Context): Boolean {
val cm = context.getSystemService(ConnectivityManager::class.java)
val capabilities = cm.getNetworkCapabilities(cm.activeNetwork) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
}
data class FlashDrive(

View file

@ -14,8 +14,6 @@ import android.app.backup.RestoreSet
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
import android.net.ConnectivityManager
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.annotation.VisibleForTesting
@ -431,24 +429,18 @@ internal class BackupCoordinator(
}
private fun getBackupBackoff(): Long {
val noBackoff = 0L
val longBackoff = DAYS.toMillis(30)
// back off if there's no storage set
val storage = settingsManager.getStorage() ?: return longBackoff
// back off if storage is removable and not available right now
return if (storage.isUsb && !storage.getDocumentFile(context).isDirectory) longBackoff
// back off if storage is on network, but we have no access
else if (storage.requiresNetwork && !hasInternet()) HOURS.toMillis(1)
// otherwise no back off
else noBackoff
}
private fun hasInternet(): Boolean {
val cm = context.getSystemService(ConnectivityManager::class.java)
val capabilities = cm.getNetworkCapabilities(cm.activeNetwork) ?: return false
return capabilities.hasCapability(NET_CAPABILITY_INTERNET)
return when {
// back off if storage is removable and not available right now
storage.isUnavailableUsb(context) -> longBackoff
// back off if storage is on network, but we have no access
storage.isUnavailableNetwork(context) -> HOURS.toMillis(1)
// otherwise no back off
else -> 0L
}
}
}

View file

@ -283,7 +283,7 @@ internal class RestoreCoordinator(
// TODO this is plugin specific, needs to be factored out when supporting different plugins
private fun isStorageRemovableAndNotAvailable(): Boolean {
val storage = settingsManager.getStorage() ?: return false
return storage.isUsb && !storage.getDocumentFile(context).isDirectory
return storage.isUnavailableUsb(context)
}
}

View file

@ -9,7 +9,6 @@ import android.content.pm.ApplicationInfo.FLAG_STOPPED
import android.content.pm.PackageInfo
import android.net.Uri
import android.os.ParcelFileDescriptor
import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.coAssertThrows
import com.stevesoltys.seedvault.getRandomString
@ -63,7 +62,10 @@ internal class BackupCoordinatorTest : BackupTest() {
private val metadataOutputStream = mockk<OutputStream>()
private val fileDescriptor: ParcelFileDescriptor = mockk()
private val packageMetadata: PackageMetadata = mockk()
private val storage = Storage(Uri.EMPTY, getRandomString(), false, false)
private val storage = Storage(Uri.EMPTY, getRandomString(),
isUsb = false,
requiresNetwork = false
)
@Test
fun `starting a new restore set works as expected`() = runBlocking {
@ -121,14 +123,11 @@ internal class BackupCoordinatorTest : BackupTest() {
fun `no error notification when device initialization fails on unplugged USB storage`() =
runBlocking {
val storage = mockk<Storage>()
val documentFile = mockk<DocumentFile>()
every { settingsManager.getToken() } returns token
coEvery { plugin.initializeDevice() } throws IOException()
every { settingsManager.getStorage() } returns storage
every { storage.isUsb } returns true
every { storage.getDocumentFile(context) } returns documentFile
every { documentFile.isDirectory } returns false
every { storage.isUnavailableUsb(context) } returns true
assertEquals(TRANSPORT_ERROR, backup.initializeDevice())

View file

@ -8,7 +8,6 @@ import android.app.backup.RestoreDescription.TYPE_FULL_STREAM
import android.app.backup.RestoreDescription.TYPE_KEY_VALUE
import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.seedvault.coAssertThrows
import com.stevesoltys.seedvault.getRandomString
import com.stevesoltys.seedvault.metadata.BackupMetadata
@ -57,7 +56,6 @@ internal class RestoreCoordinatorTest : TransportTest() {
private val inputStream = mockk<InputStream>()
private val storage: Storage = mockk()
private val documentFile: DocumentFile = mockk()
private val packageInfo2 = PackageInfo().apply { packageName = "org.example2" }
private val packageInfoArray = arrayOf(packageInfo)
private val packageInfoArray2 = arrayOf(packageInfo, packageInfo2)
@ -124,9 +122,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test
fun `startRestore() optimized auto-restore with removed storage shows notification`() {
every { settingsManager.getStorage() } returns storage
every { storage.isUsb } returns true
every { storage.getDocumentFile(context) } returns documentFile
every { documentFile.isDirectory } returns false
every { storage.isUnavailableUsb(context) } returns true
every { metadataManager.getPackageMetadata(packageName) } returns PackageMetadata(42L)
every { storage.name } returns storageName
every {
@ -149,9 +145,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test
fun `startRestore() optimized auto-restore with available storage shows no notification`() {
every { settingsManager.getStorage() } returns storage
every { storage.isUsb } returns true
every { storage.getDocumentFile(context) } returns documentFile
every { documentFile.isDirectory } returns true
every { storage.isUnavailableUsb(context) } returns false
assertEquals(TRANSPORT_OK, restore.startRestore(token, pmPackageInfoArray))
@ -166,9 +160,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test
fun `startRestore() with removed storage shows no notification`() {
every { settingsManager.getStorage() } returns storage
every { storage.isUsb } returns true
every { storage.getDocumentFile(context) } returns documentFile
every { documentFile.isDirectory } returns false
every { storage.isUnavailableUsb(context) } returns true
every { metadataManager.getPackageMetadata(packageName) } returns null
assertEquals(TRANSPORT_ERROR, restore.startRestore(token, pmPackageInfoArray))