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