Clean up system USB storage feature a bit

This commit is contained in:
Torsten Grote 2022-03-31 13:20:24 -03:00 committed by Chirayu Desai
parent 81d5281a94
commit d598aac81e
9 changed files with 46 additions and 27 deletions

View file

@ -144,6 +144,7 @@ fun <T> permitDiskReads(func: () -> T): T {
} }
fun Context.getSystemContext(isUsbStorage: () -> Boolean): Context { fun Context.getSystemContext(isUsbStorage: () -> Boolean): Context {
return if (checkSelfPermission(INTERACT_ACROSS_USERS_FULL) == PERMISSION_GRANTED return if (checkSelfPermission(INTERACT_ACROSS_USERS_FULL) == PERMISSION_GRANTED &&
&& isUsbStorage()) createContextAsUser(UserHandle.SYSTEM, 0) else this isUsbStorage()
) createContextAsUser(UserHandle.SYSTEM, 0) else this
} }

View file

@ -21,7 +21,11 @@ internal class DocumentsProviderStoragePlugin(
private val storage: DocumentsStorage, private val storage: DocumentsStorage,
) : StoragePlugin { ) : StoragePlugin {
private val context: Context get() = appContext.getSystemContext { /**
* Attention: This context might be from a different user. Use with care.
*/
private val context: Context
get() = appContext.getSystemContext {
storage.storage?.isUsb == true storage.storage?.isUsb == true
} }

View file

@ -45,19 +45,21 @@ internal class DocumentsStorage(
private val appContext: Context, private val appContext: Context,
private val settingsManager: SettingsManager, private val settingsManager: SettingsManager,
) { ) {
private val context: Context get() = appContext.getSystemContext {
storage?.isUsb ?: false
}
private val contentResolver: ContentResolver get() = context.contentResolver
internal var storage: Storage? = null internal var storage: Storage? = null
get() { get() {
if (field == null) field = settingsManager.getStorage() if (field == null) field = settingsManager.getStorage()
return field return field
} }
/**
* Attention: This context might be from a different user. Use with care.
*/
private val context: Context
get() = appContext.getSystemContext {
storage?.isUsb == true
}
private val contentResolver: ContentResolver get() = context.contentResolver
internal var rootBackupDir: DocumentFile? = null internal var rootBackupDir: DocumentFile? = null
get() = runBlocking { get() = runBlocking {
if (field == null) { if (field == null) {

View file

@ -5,11 +5,11 @@ import android.hardware.usb.UsbDevice
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.net.Uri import android.net.Uri
import android.os.UserHandle
import androidx.annotation.UiThread import androidx.annotation.UiThread
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.stevesoltys.seedvault.getSystemContext
import com.stevesoltys.seedvault.permitDiskReads import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
import java.util.concurrent.ConcurrentSkipListSet import java.util.concurrent.ConcurrentSkipListSet
@ -122,8 +122,8 @@ class SettingsManager(private val context: Context) {
@WorkerThread @WorkerThread
fun canDoBackupNow(): Boolean { fun canDoBackupNow(): Boolean {
val storage = getStorage() ?: return false val storage = getStorage() ?: return false
return !storage.isUnavailableUsb(context.createContextAsUser(UserHandle.SYSTEM, 0)) val systemContext = context.getSystemContext { storage.isUsb }
&& !storage.isUnavailableNetwork(context) return !storage.isUnavailableUsb(systemContext) && !storage.isUnavailableNetwork(context)
} }
fun backupApks(): Boolean { fun backupApks(): Boolean {

View file

@ -13,6 +13,9 @@ internal class SeedvaultStoragePlugin(
private val storage: DocumentsStorage, private val storage: DocumentsStorage,
private val keyManager: KeyManager, private val keyManager: KeyManager,
) : SafStoragePlugin(appContext) { ) : SafStoragePlugin(appContext) {
/**
* Attention: This context might be from a different user. Use with care.
*/
override val context: Context override val context: Context
get() = appContext.getSystemContext { get() = appContext.getSystemContext {
storage.storage?.isUsb == true storage.storage?.isUsb == true

View file

@ -18,14 +18,13 @@ import android.provider.DocumentsContract.Root.FLAG_SUPPORTS_CREATE
import android.provider.DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD import android.provider.DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
import android.util.Log import android.util.Log
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.getSystemContext
import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption
internal object StorageRootResolver { internal object StorageRootResolver {
private val TAG = StorageRootResolver::class.java.simpleName private val TAG = StorageRootResolver::class.java.simpleName
private const val usbAuthority = "com.android.externalstorage.documents"
fun getStorageRoots(context: Context, authority: String): List<SafOption> { fun getStorageRoots(context: Context, authority: String): List<SafOption> {
val roots = ArrayList<SafOption>() val roots = ArrayList<SafOption>()
val rootsUri = DocumentsContract.buildRootsUri(authority) val rootsUri = DocumentsContract.buildRootsUri(authority)
@ -37,12 +36,17 @@ internal object StorageRootResolver {
if (root != null) roots.add(root) if (root != null) roots.add(root)
} }
} }
if (usbAuthority == authority && UserHandle.myUserId() != UserHandle.USER_SYSTEM) { // add special system user roots for USB devices
val c: Context = context.createContextAsUser(UserHandle.SYSTEM, 0) val c = context.getSystemContext {
authority == AUTHORITY_STORAGE && UserHandle.myUserId() != UserHandle.USER_SYSTEM
}
// only proceed if we really got a different [Context], e.g. had permission for it
if (context !== c) {
c.contentResolver.query(rootsUri, null, null, null, null)?.use { cursor -> c.contentResolver.query(rootsUri, null, null, null, null)?.use { cursor ->
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
// Pass in context since it is used to query package manager for app icons // Pass in [context] since it is used to query package manager for app icons
val root = getStorageRoot(context, authority, cursor) val root = getStorageRoot(context, authority, cursor)
// only add USB storage from system user, no others
if (root != null && root.isUsb) roots.add(root) if (root != null && root.isUsb) roots.add(root)
} }
} }

View file

@ -39,6 +39,7 @@ internal class StoragePluginTest : BackupTest() {
// get current set dir and for that the current token // get current set dir and for that the current token
every { storage getProperty "currentToken" } returns token every { storage getProperty "currentToken" } returns token
every { settingsManager.getToken() } returns token every { settingsManager.getToken() } returns token
every { storage getProperty "storage" } returns null // just to check if isUsb
coEvery { storage.getSetDir(token) } returns setDir coEvery { storage.getSetDir(token) } returns setDir
// delete contents of current set dir // delete contents of current set dir
coEvery { setDir.listFilesBlocking(context) } returns listOf(backupFile) coEvery { setDir.listFilesBlocking(context) } returns listOf(backupFile)

View file

@ -11,7 +11,7 @@ import javax.crypto.SecretKey
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
class TestSafStoragePlugin( class TestSafStoragePlugin(
private val appContext: Context, appContext: Context,
private val getLocationUri: () -> Uri?, private val getLocationUri: () -> Uri?,
) : SafStoragePlugin(appContext) { ) : SafStoragePlugin(appContext) {

View file

@ -35,12 +35,15 @@ private const val TAG = "SafStoragePlugin"
public abstract class SafStoragePlugin( public abstract class SafStoragePlugin(
private val appContext: Context, private val appContext: Context,
) : StoragePlugin { ) : StoragePlugin {
/**
private val cache = SafCache() * Attention: This context could be unexpected. E.g. the system user's application context,
// In the case of USB storage, if INTERACT_ACROSS_USERS_FULL is granted, this context will match * in the case of USB storage, if INTERACT_ACROSS_USERS_FULL permission is granted.
// the system user's application context. Otherwise, matches appContext. * Use [appContext], if you need the context of the current app and user
* and [context] for all file access.
*/
protected abstract val context: Context protected abstract val context: Context
protected abstract val root: DocumentFile? protected abstract val root: DocumentFile?
private val cache = SafCache()
private val folder: DocumentFile? private val folder: DocumentFile?
get() { get() {
@ -48,8 +51,9 @@ public abstract class SafStoragePlugin(
if (cache.currentFolder != null) return cache.currentFolder if (cache.currentFolder != null) return cache.currentFolder
@SuppressLint("HardwareIds") @SuppressLint("HardwareIds")
// this is unique to each combination of app-signing key, user, and device // This is unique to each combination of app-signing key, user, and device
// so we don't leak anything by not hashing this and can use it as is // so we don't leak anything by not hashing this and can use it as is.
// Note: Use [appContext] here to not get the wrong ID for a different user.
val androidId = Settings.Secure.getString(appContext.contentResolver, ANDROID_ID) val androidId = Settings.Secure.getString(appContext.contentResolver, ANDROID_ID)
// the folder name is our user ID // the folder name is our user ID
val folderName = "$androidId.sv" val folderName = "$androidId.sv"