Skip to content

Commit

Permalink
Fixed: In Samsung tablet of Android 14, downloaded files can not be o…
Browse files Browse the repository at this point in the history
…pened 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.
  • Loading branch information
MohitMaliDeveloper committed Oct 2, 2024
1 parent 38400df commit 710272f
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,15 @@ class FileUtilsInstrumentationTest {
Uri.parse(
"${downloadUriPrefix}0"
)
),
DummyUrlData(
null,
null,
null,
null,
Uri.parse(
"${downloadDocumentUriPrefix}msf%3A1000000057"
)
)
)
context?.let { context ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ import android.content.ContentUris
import android.content.Context
import android.content.Intent
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 android.widget.Toast
import androidx.core.content.ContextCompat
Expand Down Expand Up @@ -144,7 +147,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
}
Expand All @@ -155,6 +158,73 @@ object FileUtils {
return null
}

private fun getFilePathOfContentUri(context: Context, uri: Uri): String? {
val filePath = contentQuery(context, uri)
return if (!filePath.isNullOrEmpty()) {
filePath

Check warning on line 164 in core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt#L164

Added line #L164 was not covered by tests
} 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

Check warning on line 180 in core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt#L180

Added line #L180 was not covered by tests
}
}
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)}/"

Check warning on line 196 in core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt#L195-L196

Added lines #L195 - L196 were not covered by tests
}
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)

Check warning on line 217 in core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt#L216-L217

Added lines #L216 - L217 were not covered by tests
} else {
null
}
} catch (ignore: Exception) {
null
} finally {
cursor?.close()
}
}

fun documentProviderContentQuery(
context: Context,
uri: Uri,
Expand Down Expand Up @@ -184,7 +254,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(
Expand Down

0 comments on commit 710272f

Please sign in to comment.