DroidFS/app/src/main/java/sushi/hardcore/droidfs/content_providers/TemporaryFileProvider.kt

147 lines
5.1 KiB
Kotlin
Raw Normal View History

2023-08-20 14:56:46 +02:00
package sushi.hardcore.droidfs.content_providers
import android.content.ContentProvider
import android.content.ContentValues
2023-09-06 19:27:41 +02:00
import android.content.Intent
2023-08-20 14:56:46 +02:00
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
2023-09-06 19:27:41 +02:00
import android.os.ParcelFileDescriptor
2023-08-20 14:56:46 +02:00
import android.provider.OpenableColumns
2023-09-06 19:27:41 +02:00
import android.util.Log
2023-08-20 14:56:46 +02:00
import android.webkit.MimeTypeMap
2023-09-06 19:27:41 +02:00
import androidx.preference.PreferenceManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import sushi.hardcore.droidfs.BuildConfig
import sushi.hardcore.droidfs.EncryptedFileProvider
import sushi.hardcore.droidfs.VolumeManager
import sushi.hardcore.droidfs.VolumeManagerApp
import sushi.hardcore.droidfs.util.Wiper
2023-08-20 14:56:46 +02:00
import java.io.File
2023-09-06 19:27:41 +02:00
import java.util.UUID
2023-08-20 14:56:46 +02:00
2023-09-06 19:27:41 +02:00
class TemporaryFileProvider : ContentProvider() {
private inner class ProvidedFile(
val file: EncryptedFileProvider.ExportedFile,
val size: Long,
val volumeId: Int
)
2023-08-20 14:56:46 +02:00
2023-09-06 19:27:41 +02:00
companion object {
private const val TAG = "TemporaryFileProvider"
private const val AUTHORITY = BuildConfig.APPLICATION_ID + ".temporary_provider"
private val BASE_URI: Uri = Uri.parse("content://$AUTHORITY")
2023-08-20 14:56:46 +02:00
2023-09-06 19:27:41 +02:00
lateinit var instance: TemporaryFileProvider
private set
var usfSafWrite = false
}
private lateinit var volumeManager: VolumeManager
lateinit var encryptedFileProvider: EncryptedFileProvider
private val files = HashMap<Uri, ProvidedFile>()
override fun onCreate(): Boolean {
return context?.let {
volumeManager = (it.applicationContext as VolumeManagerApp).volumeManager
usfSafWrite =
PreferenceManager.getDefaultSharedPreferences(it).getBoolean("usf_saf_write", false)
encryptedFileProvider = EncryptedFileProvider(it)
instance = this
val tmpFilesDir = EncryptedFileProvider.getTmpFilesDir(it)
val success = tmpFilesDir.mkdirs()
// wipe any additional files not previously deleted
GlobalScope.launch(Dispatchers.IO) {
tmpFilesDir.listFiles()?.onEach { f -> Wiper.wipe(f) }
}
success
} ?: false
}
fun exportFile(
exportedFile: EncryptedFileProvider.ExportedFile,
size: Long,
volumeId: Int
): Uri? {
if (!encryptedFileProvider.exportFile(exportedFile, volumeManager.getVolume(volumeId)!!)) {
return null
}
return Uri.withAppendedPath(BASE_URI, UUID.randomUUID().toString()).also {
files[it] = ProvidedFile(exportedFile, size, volumeId)
}
}
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? {
val file = files[uri] ?: return null
2023-08-20 14:56:46 +02:00
return MatrixCursor(arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE), 1).apply {
2023-09-06 19:27:41 +02:00
addRow(arrayOf(File(file.file.path).name, file.size))
2023-08-20 14:56:46 +02:00
}
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
throw UnsupportedOperationException("Operation not supported")
}
2023-09-06 19:27:41 +02:00
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<String>?
): Int {
2023-08-20 14:56:46 +02:00
throw UnsupportedOperationException("Operation not supported")
}
2023-09-06 19:27:41 +02:00
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
return if (files.remove(uri)?.file?.also { it.free() } == null) 0 else 1
}
override fun getType(uri: Uri): String = files[uri]?.file?.path?.let {
2023-08-20 14:56:46 +02:00
MimeTypeMap.getSingleton().getMimeTypeFromExtension(File(it).extension)
} ?: "application/octet-stream"
2023-09-06 19:27:41 +02:00
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
files[uri]?.let { file ->
val encryptedVolume = volumeManager.getVolume(file.volumeId) ?: run {
Log.e(TAG, "Volume closed for $uri")
return null
}
val result = encryptedFileProvider.openFile(
file.file,
mode,
encryptedVolume,
volumeManager.getCoroutineScope(file.volumeId),
false,
usfSafWrite,
)
when (result.second) {
EncryptedFileProvider.Error.SUCCESS -> return result.first!!
EncryptedFileProvider.Error.WRITE_ACCESS_DENIED -> Log.e(
TAG,
"Unauthorized write access requested from $callingPackage to $uri"
)
else -> result.second.log()
}
}
return null
}
// this must not be cancelled
fun wipe() = GlobalScope.launch(Dispatchers.IO) {
context!!.revokeUriPermission(BASE_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
synchronized(this@TemporaryFileProvider) {
for (i in files.values) {
i.file.free()
}
files.clear()
}
}
2023-08-20 14:56:46 +02:00
}