Fixed: In Samsung tablet of Android 14, downloaded files can not be opened via file picker or deep linking.

* When we open a file from different browsers, they provide a URI through their own file provider, and the content resolver cannot retrieve the file path for these types of URIs. To fix this issue, we have introduced a fallback method that returns the exact path of the file located in the Downloads folder.
* Another issue we encountered on tablets is that the URIs are different from those on regular mobile devices, and our `documentProviderContentQuery` method could not return the path for these types of URIs from the Downloads folder. To fix this issue, we used our fallback method to retrieve the file path for these URIs.
This commit is contained in:
MohitMaliFtechiz 2024-10-02 18:55:42 +05:30 committed by Kelson
parent eaa28d66df
commit 777c0fff4b
2 changed files with 88 additions and 2 deletions

View File

@ -406,6 +406,15 @@ class FileUtilsInstrumentationTest {
Uri.parse(
"${downloadUriPrefix}0"
)
),
DummyUrlData(
null,
null,
null,
null,
Uri.parse(
"${downloadDocumentUriPrefix}msf%3A1000000057"
)
)
)
context?.let { context ->

View File

@ -21,10 +21,13 @@ import android.annotation.SuppressLint
import android.content.ContentUris
import android.content.Context
import android.content.res.AssetFileDescriptor
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.storage.StorageManager
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.webkit.URLUtil
import androidx.core.content.ContextCompat
import kotlinx.coroutines.CoroutineScope
@ -143,7 +146,7 @@ object FileUtils {
}
} else if (uri.scheme != null) {
if ("content".equals(uri.scheme, ignoreCase = true)) {
return contentQuery(context, uri)
return getFilePathOfContentUri(context, uri)
} else if ("file".equals(uri.scheme, ignoreCase = true)) {
return uri.path
}
@ -154,6 +157,73 @@ object FileUtils {
return null
}
private fun getFilePathOfContentUri(context: Context, uri: Uri): String? {
val filePath = contentQuery(context, uri)
return if (!filePath.isNullOrEmpty()) {
filePath
} else {
// Fallback method to get the actual path of the URI. This will be called
// when contentQuery returns null, especially in cases where the user directly clicks
// on the downloaded file within browsers (since different browsers provide URIs with their
// own file providers, the content resolver cannot directly retrieve paths for those URIs).
val fileName = getFileNameFromUri(context, uri)
getFilePathFromFileName(context, fileName)
}
}
private fun getFilePathFromFileName(context: Context, fileName: String?): String? {
var filePath: String? = null
getStorageVolumesList(context).forEach { volume ->
val file = File("$volume/Download/$fileName")
if (file.isFileExist()) {
filePath = file.path
}
}
return filePath
}
private fun getStorageVolumesList(context: Context): HashSet<String> {
val storageVolumes = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
val storageVolumesList = HashSet<String>()
storageVolumes.storageVolumes.filterNotNull().forEach {
if (it.isPrimary) {
storageVolumesList.add("${Environment.getExternalStorageDirectory()}/")
} else {
val externalStorageName = it.uuid?.let { uuid ->
"/$uuid/"
} ?: kotlin.run {
"/${it.getDescription(context)}/"
}
storageVolumesList.add(externalStorageName)
}
}
return storageVolumesList
}
private fun getFileNameFromUri(context: Context, uri: Uri?): String? {
if (uri == null) return null
var cursor: Cursor? = null
val projection = arrayOf(
MediaStore.MediaColumns.DISPLAY_NAME
)
return try {
cursor = context.contentResolver.query(
uri, projection, null, null,
null
)
if (cursor != null && cursor.moveToFirst()) {
val index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
cursor.getString(index)
} else {
null
}
} catch (ignore: Exception) {
null
} finally {
cursor?.close()
}
}
fun documentProviderContentQuery(
context: Context,
uri: Uri,
@ -183,7 +253,14 @@ object FileUtils {
actualDocumentId,
contentUriPrefixes,
documentsContractWrapper
)
) ?: kotlin.run {
// Fallback method to get the actual path of the URI. This will be called
// when queryForActualPath returns null, especially in cases where the user directly opens
// the file from the file manager in the downloads folder, and the URI contains a different
// document ID (particularly on tablets). See https://github.com/kiwix/kiwix-android/issues/4008
val fileName = getFileNameFromUri(context, uri)
getFilePathFromFileName(context, fileName)
}
}
private fun queryForActualPath(