viewer: handle file media URI
This commit is contained in:
parent
82b92e79f4
commit
2f92138342
2 changed files with 45 additions and 17 deletions
|
@ -83,7 +83,7 @@ abstract class ImageProvider {
|
||||||
try {
|
try {
|
||||||
val exif = ExifInterface(editablePath)
|
val exif = ExifInterface(editablePath)
|
||||||
// when the orientation is not defined, it returns `undefined (0)` instead of the orientation default value `normal (1)`
|
// when the orientation is not defined, it returns `undefined (0)` instead of the orientation default value `normal (1)`
|
||||||
// in that case we explicitely set it to `normal` first
|
// in that case we explicitly set it to `normal` first
|
||||||
// because ExifInterface fails to rotate an image with undefined orientation
|
// because ExifInterface fails to rotate an image with undefined orientation
|
||||||
// as of androidx.exifinterface:exifinterface:1.3.0
|
// as of androidx.exifinterface:exifinterface:1.3.0
|
||||||
val currentOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
|
val currentOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package deckers.thibault.aves.model.provider
|
package deckers.thibault.aves.model.provider
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.ContentUris
|
import android.content.ContentUris
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -49,6 +50,11 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
val contentUri = ContentUris.withAppendedId(VIDEO_CONTENT_URI, id)
|
val contentUri = ContentUris.withAppendedId(VIDEO_CONTENT_URI, id)
|
||||||
if (fetchFrom(context, alwaysValid, onSuccess, contentUri, VIDEO_PROJECTION) > 0) return
|
if (fetchFrom(context, alwaysValid, onSuccess, contentUri, VIDEO_PROJECTION) > 0) return
|
||||||
}
|
}
|
||||||
|
// the uri can be a file media uri (e.g. "content://0@media/external/file/30050")
|
||||||
|
// without an equivalent image/video if it is shared from a file browser
|
||||||
|
// but the file is not publicly visible
|
||||||
|
if (fetchFrom(context, alwaysValid, onSuccess, uri, BASE_PROJECTION) > 0) return
|
||||||
|
|
||||||
callback.onFailure(Exception("failed to fetch entry at uri=$uri"))
|
callback.onFailure(Exception("failed to fetch entry at uri=$uri"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,30 +96,36 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
try {
|
try {
|
||||||
val cursor = context.contentResolver.query(contentUri, projection, null, null, orderBy)
|
val cursor = context.contentResolver.query(contentUri, projection, null, null, orderBy)
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
|
val contentUriContainsId = when (contentUri) {
|
||||||
|
IMAGE_CONTENT_URI, VIDEO_CONTENT_URI -> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
|
||||||
// image & video
|
// image & video
|
||||||
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
|
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
|
||||||
val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
|
val pathColumn = cursor.getColumnIndexOrThrow(MediaColumns.PATH)
|
||||||
val mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)
|
val mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)
|
||||||
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)
|
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)
|
||||||
val titleColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.TITLE)
|
val titleColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.TITLE)
|
||||||
val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH)
|
val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH)
|
||||||
val heightColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.HEIGHT)
|
val heightColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.HEIGHT)
|
||||||
val dateModifiedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
|
val dateModifiedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
|
||||||
val dateTakenColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_TAKEN)
|
val dateTakenColumn = cursor.getColumnIndex(MediaColumns.DATE_TAKEN)
|
||||||
|
|
||||||
// image & video for API >= Q, only for images for API < Q
|
// image & video for API >= Q, only for images for API < Q
|
||||||
val orientationColumn = cursor.getColumnIndex(MediaStore.MediaColumns.ORIENTATION)
|
val orientationColumn = cursor.getColumnIndex(MediaColumns.ORIENTATION)
|
||||||
|
|
||||||
// video only
|
// video only
|
||||||
val durationColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DURATION)
|
val durationColumn = cursor.getColumnIndex(MediaColumns.DURATION)
|
||||||
val needDuration = projection.contentEquals(VIDEO_PROJECTION)
|
val needDuration = projection.contentEquals(VIDEO_PROJECTION)
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val contentId = cursor.getInt(idColumn)
|
val contentId = cursor.getInt(idColumn)
|
||||||
val dateModifiedSecs = cursor.getInt(dateModifiedColumn)
|
val dateModifiedSecs = cursor.getInt(dateModifiedColumn)
|
||||||
if (isValidEntry(contentId, dateModifiedSecs)) {
|
if (isValidEntry(contentId, dateModifiedSecs)) {
|
||||||
// building `itemUri` this way is fine if `contentUri` does not already contain the ID
|
// for multiple items, `contentUri` is the root without ID,
|
||||||
val itemUri = ContentUris.withAppendedId(contentUri, contentId.toLong())
|
// but for single items, `contentUri` already contains the ID
|
||||||
|
val itemUri = if (contentUriContainsId) contentUri else ContentUris.withAppendedId(contentUri, contentId.toLong())
|
||||||
val mimeType = cursor.getString(mimeTypeColumn)
|
val mimeType = cursor.getString(mimeTypeColumn)
|
||||||
val width = cursor.getInt(widthColumn)
|
val width = cursor.getInt(widthColumn)
|
||||||
val height = cursor.getInt(heightColumn)
|
val height = cursor.getInt(heightColumn)
|
||||||
|
@ -129,7 +141,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
"sizeBytes" to cursor.getLong(sizeColumn),
|
"sizeBytes" to cursor.getLong(sizeColumn),
|
||||||
"title" to cursor.getString(titleColumn),
|
"title" to cursor.getString(titleColumn),
|
||||||
"dateModifiedSecs" to dateModifiedSecs,
|
"dateModifiedSecs" to dateModifiedSecs,
|
||||||
"sourceDateTakenMillis" to cursor.getLong(dateTakenColumn),
|
"sourceDateTakenMillis" to if (dateTakenColumn != -1) cursor.getLong(dateTakenColumn) else null,
|
||||||
"durationMillis" to durationMillis,
|
"durationMillis" to durationMillis,
|
||||||
// only for map export
|
// only for map export
|
||||||
"contentId" to contentId,
|
"contentId" to contentId,
|
||||||
|
@ -337,34 +349,50 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
|
|
||||||
private val BASE_PROJECTION = arrayOf(
|
private val BASE_PROJECTION = arrayOf(
|
||||||
MediaStore.MediaColumns._ID,
|
MediaStore.MediaColumns._ID,
|
||||||
MediaStore.MediaColumns.DATA,
|
MediaColumns.PATH,
|
||||||
MediaStore.MediaColumns.MIME_TYPE,
|
MediaStore.MediaColumns.MIME_TYPE,
|
||||||
MediaStore.MediaColumns.SIZE, // TODO TLAD use `DISPLAY_NAME` instead/along `TITLE`?
|
MediaStore.MediaColumns.SIZE, // TODO TLAD use `DISPLAY_NAME` instead/along `TITLE`?
|
||||||
MediaStore.MediaColumns.TITLE,
|
MediaStore.MediaColumns.TITLE,
|
||||||
MediaStore.MediaColumns.WIDTH,
|
MediaStore.MediaColumns.WIDTH,
|
||||||
MediaStore.MediaColumns.HEIGHT,
|
MediaStore.MediaColumns.HEIGHT,
|
||||||
MediaStore.MediaColumns.DATE_MODIFIED
|
MediaStore.MediaColumns.DATE_MODIFIED,
|
||||||
|
MediaColumns.DATE_TAKEN,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val IMAGE_PROJECTION = arrayOf(
|
private val IMAGE_PROJECTION = arrayOf(
|
||||||
*BASE_PROJECTION,
|
*BASE_PROJECTION,
|
||||||
// uses `MediaStore.Images.Media` instead of `MediaStore.MediaColumns` for APIs < Q
|
MediaColumns.ORIENTATION,
|
||||||
MediaStore.Images.Media.DATE_TAKEN,
|
|
||||||
MediaStore.Images.Media.ORIENTATION
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private val VIDEO_PROJECTION = arrayOf(
|
private val VIDEO_PROJECTION = arrayOf(
|
||||||
*BASE_PROJECTION,
|
*BASE_PROJECTION,
|
||||||
// uses `MediaStore.Video.Media` instead of `MediaStore.MediaColumns` for APIs < Q
|
MediaColumns.DURATION,
|
||||||
MediaStore.Video.Media.DATE_TAKEN,
|
// `ORIENTATION` was only available for images before Android Q
|
||||||
MediaStore.Video.Media.DURATION,
|
|
||||||
*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) arrayOf(
|
*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) arrayOf(
|
||||||
MediaStore.Video.Media.ORIENTATION
|
MediaStore.MediaColumns.ORIENTATION,
|
||||||
) else emptyArray()
|
) else emptyArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object MediaColumns {
|
||||||
|
// `DATE_TAKEN`, `ORIENTATION`, `DURATION` used to be in `MediaStore.[Images,Video].Media`
|
||||||
|
// but were moved to `MediaStore.MediaColumns` for API 29
|
||||||
|
// it is safe to use them because they are static strings that have not changed
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
const val DATE_TAKEN = MediaStore.MediaColumns.DATE_TAKEN
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
const val ORIENTATION = MediaStore.MediaColumns.ORIENTATION
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
const val DURATION = MediaStore.MediaColumns.DURATION
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
const val PATH = MediaStore.MediaColumns.DATA
|
||||||
|
}
|
||||||
|
|
||||||
typealias NewEntryHandler = (entry: FieldMap) -> Unit
|
typealias NewEntryHandler = (entry: FieldMap) -> Unit
|
||||||
|
|
||||||
private typealias NewEntryChecker = (contentId: Int, dateModifiedSecs: Int) -> Boolean
|
private typealias NewEntryChecker = (contentId: Int, dateModifiedSecs: Int) -> Boolean
|
Loading…
Reference in a new issue