Clean up system USB storage feature a bit
This commit is contained in:
parent
81d5281a94
commit
d598aac81e
9 changed files with 46 additions and 27 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue