#1084 crashfix for png with large exif via ExifInterface,
fixed ExifInterface disambiguation, improved safe mode
This commit is contained in:
parent
a38c5b72ee
commit
2d143f139f
22 changed files with 75 additions and 55 deletions
|
@ -211,6 +211,7 @@ dependencies {
|
||||||
implementation 'com.github.deckerst.mp4parser:isoparser:4cc0c5d06c'
|
implementation 'com.github.deckerst.mp4parser:isoparser:4cc0c5d06c'
|
||||||
implementation 'com.github.deckerst.mp4parser:muxer:4cc0c5d06c'
|
implementation 'com.github.deckerst.mp4parser:muxer:4cc0c5d06c'
|
||||||
implementation 'com.github.deckerst:pixymeta-android:9ec7097f17'
|
implementation 'com.github.deckerst:pixymeta-android:9ec7097f17'
|
||||||
|
implementation project(':exifinterface')
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
|
||||||
import com.drew.metadata.file.FileTypeDirectory
|
import com.drew.metadata.file.FileTypeDirectory
|
||||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||||
import deckers.thibault.aves.metadata.ExifInterfaceHelper
|
import deckers.thibault.aves.metadata.ExifInterfaceHelper
|
||||||
|
|
|
@ -6,7 +6,7 @@ import android.media.MediaMetadataRetriever
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
|
||||||
import com.adobe.internal.xmp.XMPException
|
import com.adobe.internal.xmp.XMPException
|
||||||
import com.adobe.internal.xmp.XMPMeta
|
import com.adobe.internal.xmp.XMPMeta
|
||||||
import com.adobe.internal.xmp.XMPMetaFactory
|
import com.adobe.internal.xmp.XMPMetaFactory
|
||||||
|
|
|
@ -21,11 +21,13 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
|
||||||
|
|
||||||
private var knownEntries: Map<Long?, Int?>? = null
|
private var knownEntries: Map<Long?, Int?>? = null
|
||||||
private var directory: String? = null
|
private var directory: String? = null
|
||||||
|
private var safe: Boolean = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (arguments is Map<*, *>) {
|
if (arguments is Map<*, *>) {
|
||||||
knownEntries = (arguments["knownEntries"] as? Map<*, *>?)?.map { (it.key as Number?)?.toLong() to it.value as Int? }?.toMap()
|
knownEntries = (arguments["knownEntries"] as? Map<*, *>?)?.map { (it.key as Number?)?.toLong() to it.value as Int? }?.toMap()
|
||||||
directory = arguments["directory"] as String?
|
directory = arguments["directory"] as String?
|
||||||
|
safe = arguments.getOrDefault("safe", false) as Boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +61,7 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchAll() {
|
private fun fetchAll() {
|
||||||
MediaStoreImageProvider().fetchAll(context, knownEntries ?: emptyMap(), directory) { success(it) }
|
MediaStoreImageProvider().fetchAll(context, knownEntries ?: emptyMap(), directory, safe) { success(it) }
|
||||||
endOfStream()
|
endOfStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package deckers.thibault.aves.metadata
|
package deckers.thibault.aves.metadata
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
|
||||||
import com.drew.lang.Rational
|
import com.drew.lang.Rational
|
||||||
import com.drew.metadata.Directory
|
import com.drew.metadata.Directory
|
||||||
import com.drew.metadata.exif.ExifDirectoryBase
|
import com.drew.metadata.exif.ExifDirectoryBase
|
||||||
|
|
|
@ -2,7 +2,7 @@ package deckers.thibault.aves.metadata
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
|
||||||
import deckers.thibault.aves.utils.FileUtils.transferFrom
|
import deckers.thibault.aves.utils.FileUtils.transferFrom
|
||||||
import deckers.thibault.aves.utils.MimeTypes
|
import deckers.thibault.aves.utils.MimeTypes
|
||||||
import deckers.thibault.aves.utils.StorageUtils
|
import deckers.thibault.aves.utils.StorageUtils
|
||||||
|
|
|
@ -9,7 +9,7 @@ import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
|
||||||
import com.adobe.internal.xmp.XMPMeta
|
import com.adobe.internal.xmp.XMPMeta
|
||||||
import com.drew.imaging.jpeg.JpegSegmentType
|
import com.drew.imaging.jpeg.JpegSegmentType
|
||||||
import com.drew.metadata.exif.ExifDirectoryBase
|
import com.drew.metadata.exif.ExifDirectoryBase
|
||||||
|
|
|
@ -5,7 +5,7 @@ import android.content.Context
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.media.MediaMetadataRetriever
|
import android.media.MediaMetadataRetriever
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
|
||||||
import com.drew.metadata.avi.AviDirectory
|
import com.drew.metadata.avi.AviDirectory
|
||||||
import com.drew.metadata.exif.ExifIFD0Directory
|
import com.drew.metadata.exif.ExifIFD0Directory
|
||||||
import com.drew.metadata.jpeg.JpegDirectory
|
import com.drew.metadata.jpeg.JpegDirectory
|
||||||
|
@ -116,8 +116,8 @@ class SourceEntry {
|
||||||
// metadata retrieval
|
// metadata retrieval
|
||||||
// expects entry with: uri, mimeType
|
// expects entry with: uri, mimeType
|
||||||
// finds: width, height, orientation/rotation, date, title, duration
|
// finds: width, height, orientation/rotation, date, title, duration
|
||||||
fun fillPreCatalogMetadata(context: Context): SourceEntry {
|
fun fillPreCatalogMetadata(context: Context, safe: Boolean): SourceEntry {
|
||||||
if (isSvg) return this
|
if (isSvg || safe) return this
|
||||||
if (isVideo) {
|
if (isVideo) {
|
||||||
fillVideoByMediaMetadataRetriever(context)
|
fillVideoByMediaMetadataRetriever(context)
|
||||||
if (isSized && hasDuration) return this
|
if (isSized && hasDuration) return this
|
||||||
|
|
|
@ -52,7 +52,7 @@ internal class FileImageProvider : ImageProvider() {
|
||||||
callback.onFailure(e)
|
callback.onFailure(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entry.fillPreCatalogMetadata(context)
|
entry.fillPreCatalogMetadata(context, safe = false)
|
||||||
|
|
||||||
if (allowUnsized || entry.isSized || entry.isSvg || entry.isVideo) {
|
if (allowUnsized || entry.isSized || entry.isSvg || entry.isVideo) {
|
||||||
callback.onSuccess(entry.toMap())
|
callback.onSuccess(entry.toMap())
|
||||||
|
|
|
@ -11,7 +11,7 @@ import android.net.Uri
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.DecodeFormat
|
import com.bumptech.glide.load.DecodeFormat
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
|
|
@ -51,8 +51,10 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
context: Context,
|
context: Context,
|
||||||
knownEntries: Map<Long?, Int?>,
|
knownEntries: Map<Long?, Int?>,
|
||||||
directory: String?,
|
directory: String?,
|
||||||
|
safe: Boolean,
|
||||||
handleNewEntry: NewEntryHandler,
|
handleNewEntry: NewEntryHandler,
|
||||||
) {
|
) {
|
||||||
|
Log.d(LOG_TAG, "fetching all media store items for ${knownEntries.size} known entries, directory=$directory safe=$safe")
|
||||||
val isModified = fun(contentId: Long, dateModifiedSecs: Int): Boolean {
|
val isModified = fun(contentId: Long, dateModifiedSecs: Int): Boolean {
|
||||||
val knownDate = knownEntries[contentId]
|
val knownDate = knownEntries[contentId]
|
||||||
return knownDate == null || knownDate < dateModifiedSecs
|
return knownDate == null || knownDate < dateModifiedSecs
|
||||||
|
@ -82,8 +84,8 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
} else {
|
} else {
|
||||||
handleNew = handleNewEntry
|
handleNew = handleNewEntry
|
||||||
}
|
}
|
||||||
fetchFrom(context, isModified, handleNew, IMAGE_CONTENT_URI, IMAGE_PROJECTION, selection, selectionArgs)
|
fetchFrom(context, isModified, handleNew, IMAGE_CONTENT_URI, IMAGE_PROJECTION, selection, selectionArgs, safe = safe)
|
||||||
fetchFrom(context, isModified, handleNew, VIDEO_CONTENT_URI, VIDEO_PROJECTION, selection, selectionArgs)
|
fetchFrom(context, isModified, handleNew, VIDEO_CONTENT_URI, VIDEO_PROJECTION, selection, selectionArgs, safe = safe)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the provided URI can point to the wrong media collection,
|
// the provided URI can point to the wrong media collection,
|
||||||
|
@ -206,6 +208,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
selection: String? = null,
|
selection: String? = null,
|
||||||
selectionArgs: Array<String>? = null,
|
selectionArgs: Array<String>? = null,
|
||||||
fileMimeType: String? = null,
|
fileMimeType: String? = null,
|
||||||
|
safe: Boolean = false,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
var found = false
|
var found = false
|
||||||
val orderBy = "${MediaStore.MediaColumns.DATE_MODIFIED} DESC"
|
val orderBy = "${MediaStore.MediaColumns.DATE_MODIFIED} DESC"
|
||||||
|
@ -299,7 +302,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
// missing some attributes such as width, height, orientation.
|
// missing some attributes such as width, height, orientation.
|
||||||
// Also, the reported size of raw images is inconsistent across devices
|
// Also, the reported size of raw images is inconsistent across devices
|
||||||
// and Android versions (sometimes the raw size, sometimes the decoded size).
|
// and Android versions (sometimes the raw size, sometimes the decoded size).
|
||||||
val entry = SourceEntry(entryMap).fillPreCatalogMetadata(context)
|
val entry = SourceEntry(entryMap).fillPreCatalogMetadata(context, safe)
|
||||||
entryMap = entry.toMap()
|
entryMap = entry.toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ open class UnknownContentProvider : ImageProvider() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val entry = SourceEntry(fields).fillPreCatalogMetadata(context)
|
val entry = SourceEntry(fields).fillPreCatalogMetadata(context, safe = false)
|
||||||
if (allowUnsized || entry.isSized || entry.isSvg || entry.isVideo) {
|
if (allowUnsized || entry.isSized || entry.isSvg || entry.isVideo) {
|
||||||
callback.onSuccess(entry.toMap())
|
callback.onSuccess(entry.toMap())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package deckers.thibault.aves.utils
|
package deckers.thibault.aves.utils
|
||||||
|
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
|
||||||
import deckers.thibault.aves.decoder.MultiPageImage
|
import deckers.thibault.aves.decoder.MultiPageImage
|
||||||
|
|
||||||
object MimeTypes {
|
object MimeTypes {
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
|
|
||||||
package androidx.exifinterface.media;
|
package androidx.exifinterface.media;
|
||||||
|
|
||||||
import static androidx.exifinterface.media.ExifInterfaceUtils.closeFileDescriptor;
|
import static androidx.exifinterface.media.ExifInterfaceUtilsFork.closeFileDescriptor;
|
||||||
import static androidx.exifinterface.media.ExifInterfaceUtils.closeQuietly;
|
import static androidx.exifinterface.media.ExifInterfaceUtilsFork.closeQuietly;
|
||||||
import static androidx.exifinterface.media.ExifInterfaceUtils.convertToLongArray;
|
import static androidx.exifinterface.media.ExifInterfaceUtilsFork.convertToLongArray;
|
||||||
import static androidx.exifinterface.media.ExifInterfaceUtils.copy;
|
import static androidx.exifinterface.media.ExifInterfaceUtilsFork.copy;
|
||||||
import static androidx.exifinterface.media.ExifInterfaceUtils.parseSubSeconds;
|
import static androidx.exifinterface.media.ExifInterfaceUtilsFork.parseSubSeconds;
|
||||||
import static androidx.exifinterface.media.ExifInterfaceUtils.startsWith;
|
import static androidx.exifinterface.media.ExifInterfaceUtilsFork.startsWith;
|
||||||
import static java.nio.ByteOrder.BIG_ENDIAN;
|
import static java.nio.ByteOrder.BIG_ENDIAN;
|
||||||
import static java.nio.ByteOrder.LITTLE_ENDIAN;
|
import static java.nio.ByteOrder.LITTLE_ENDIAN;
|
||||||
|
|
||||||
|
@ -41,8 +41,8 @@ import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RestrictTo;
|
import androidx.annotation.RestrictTo;
|
||||||
import androidx.exifinterface.media.ExifInterfaceUtils.Api21Impl;
|
import androidx.exifinterface.media.ExifInterfaceUtilsFork.Api21Impl;
|
||||||
import androidx.exifinterface.media.ExifInterfaceUtils.Api23Impl;
|
import androidx.exifinterface.media.ExifInterfaceUtilsFork.Api23Impl;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
|
@ -84,6 +84,7 @@ import java.util.zip.CRC32;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Forked from 'androidx.exifinterface:exifinterface:1.3.7' on 2024/02/21
|
* Forked from 'androidx.exifinterface:exifinterface:1.3.7' on 2024/02/21
|
||||||
|
* Named differently to let ExifInterface be loaded as subdependency.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -97,7 +98,7 @@ import java.util.zip.CRC32;
|
||||||
* it. This class will search both locations for XMP data, but if XMP data exist both inside and
|
* it. This class will search both locations for XMP data, but if XMP data exist both inside and
|
||||||
* outside Exif, will favor the XMP data inside Exif over the one outside.
|
* outside Exif, will favor the XMP data inside Exif over the one outside.
|
||||||
*/
|
*/
|
||||||
public class ExifInterface {
|
public class ExifInterfaceFork {
|
||||||
// TLAD threshold for safer Exif attribute parsing
|
// TLAD threshold for safer Exif attribute parsing
|
||||||
private static final int ATTRIBUTE_SIZE_DANGER_THRESHOLD = 3 * (1 << 20); // MB
|
private static final int ATTRIBUTE_SIZE_DANGER_THRESHOLD = 3 * (1 << 20); // MB
|
||||||
|
|
||||||
|
@ -3949,7 +3950,7 @@ public class ExifInterface {
|
||||||
* @throws IOException if an I/O error occurs while retrieving file descriptor via
|
* @throws IOException if an I/O error occurs while retrieving file descriptor via
|
||||||
* {@link FileInputStream#getFD()}.
|
* {@link FileInputStream#getFD()}.
|
||||||
*/
|
*/
|
||||||
public ExifInterface(@NonNull File file) throws IOException {
|
public ExifInterfaceFork(@NonNull File file) throws IOException {
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
throw new NullPointerException("file cannot be null");
|
throw new NullPointerException("file cannot be null");
|
||||||
}
|
}
|
||||||
|
@ -3964,7 +3965,7 @@ public class ExifInterface {
|
||||||
* @throws IOException if an I/O error occurs while retrieving file descriptor via
|
* @throws IOException if an I/O error occurs while retrieving file descriptor via
|
||||||
* {@link FileInputStream#getFD()}.
|
* {@link FileInputStream#getFD()}.
|
||||||
*/
|
*/
|
||||||
public ExifInterface(@NonNull String filename) throws IOException {
|
public ExifInterfaceFork(@NonNull String filename) throws IOException {
|
||||||
if (filename == null) {
|
if (filename == null) {
|
||||||
throw new NullPointerException("filename cannot be null");
|
throw new NullPointerException("filename cannot be null");
|
||||||
}
|
}
|
||||||
|
@ -3980,7 +3981,7 @@ public class ExifInterface {
|
||||||
* @throws NullPointerException if file descriptor is null
|
* @throws NullPointerException if file descriptor is null
|
||||||
* @throws IOException if an error occurs while duplicating the file descriptor.
|
* @throws IOException if an error occurs while duplicating the file descriptor.
|
||||||
*/
|
*/
|
||||||
public ExifInterface(@NonNull FileDescriptor fileDescriptor) throws IOException {
|
public ExifInterfaceFork(@NonNull FileDescriptor fileDescriptor) throws IOException {
|
||||||
if (fileDescriptor == null) {
|
if (fileDescriptor == null) {
|
||||||
throw new NullPointerException("fileDescriptor cannot be null");
|
throw new NullPointerException("fileDescriptor cannot be null");
|
||||||
}
|
}
|
||||||
|
@ -4023,7 +4024,7 @@ public class ExifInterface {
|
||||||
* @param inputStream the input stream that contains the image data
|
* @param inputStream the input stream that contains the image data
|
||||||
* @throws NullPointerException if the input stream is null
|
* @throws NullPointerException if the input stream is null
|
||||||
*/
|
*/
|
||||||
public ExifInterface(@NonNull InputStream inputStream) throws IOException {
|
public ExifInterfaceFork(@NonNull InputStream inputStream) throws IOException {
|
||||||
this(inputStream, STREAM_TYPE_FULL_IMAGE_DATA);
|
this(inputStream, STREAM_TYPE_FULL_IMAGE_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4039,7 +4040,7 @@ public class ExifInterface {
|
||||||
* @throws IOException if an I/O error occurs while retrieving file descriptor via
|
* @throws IOException if an I/O error occurs while retrieving file descriptor via
|
||||||
* {@link FileInputStream#getFD()}.
|
* {@link FileInputStream#getFD()}.
|
||||||
*/
|
*/
|
||||||
public ExifInterface(@NonNull InputStream inputStream, @ExifStreamType int streamType)
|
public ExifInterfaceFork(@NonNull InputStream inputStream, @ExifStreamType int streamType)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (inputStream == null) {
|
if (inputStream == null) {
|
||||||
throw new NullPointerException("inputStream cannot be null");
|
throw new NullPointerException("inputStream cannot be null");
|
||||||
|
@ -5071,7 +5072,7 @@ public class ExifInterface {
|
||||||
if (location == null) {
|
if (location == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, location.getProvider());
|
setAttribute(ExifInterfaceFork.TAG_GPS_PROCESSING_METHOD, location.getProvider());
|
||||||
setLatLong(location.getLatitude(), location.getLongitude());
|
setLatLong(location.getLatitude(), location.getLongitude());
|
||||||
setAltitude(location.getAltitude());
|
setAltitude(location.getAltitude());
|
||||||
// Location objects store speeds in m/sec. Translates it to km/hr here.
|
// Location objects store speeds in m/sec. Translates it to km/hr here.
|
||||||
|
@ -5080,8 +5081,8 @@ public class ExifInterface {
|
||||||
* TimeUnit.HOURS.toSeconds(1) / 1000).toString());
|
* TimeUnit.HOURS.toSeconds(1) / 1000).toString());
|
||||||
String[] dateTime = sFormatterPrimary.format(
|
String[] dateTime = sFormatterPrimary.format(
|
||||||
new Date(location.getTime())).split("\\s+", -1);
|
new Date(location.getTime())).split("\\s+", -1);
|
||||||
setAttribute(ExifInterface.TAG_GPS_DATESTAMP, dateTime[0]);
|
setAttribute(ExifInterfaceFork.TAG_GPS_DATESTAMP, dateTime[0]);
|
||||||
setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, dateTime[1]);
|
setAttribute(ExifInterfaceFork.TAG_GPS_TIMESTAMP, dateTime[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5158,11 +5159,11 @@ public class ExifInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns parsed {@link ExifInterface#TAG_DATETIME} value as number of milliseconds since
|
* Returns parsed {@link ExifInterfaceFork#TAG_DATETIME} value as number of milliseconds since
|
||||||
* Jan. 1, 1970, midnight local time.
|
* Jan. 1, 1970, midnight local time.
|
||||||
*
|
*
|
||||||
* <p>Note: The return value includes the first three digits (or less depending on the length
|
* <p>Note: The return value includes the first three digits (or less depending on the length
|
||||||
* of the string) of {@link ExifInterface#TAG_SUBSEC_TIME}.
|
* of the string) of {@link ExifInterfaceFork#TAG_SUBSEC_TIME}.
|
||||||
*
|
*
|
||||||
* @return null if date time information is unavailable or invalid.
|
* @return null if date time information is unavailable or invalid.
|
||||||
*/
|
*/
|
||||||
|
@ -5175,11 +5176,11 @@ public class ExifInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns parsed {@link ExifInterface#TAG_DATETIME_DIGITIZED} value as number of
|
* Returns parsed {@link ExifInterfaceFork#TAG_DATETIME_DIGITIZED} value as number of
|
||||||
* milliseconds since Jan. 1, 1970, midnight local time.
|
* milliseconds since Jan. 1, 1970, midnight local time.
|
||||||
*
|
*
|
||||||
* <p>Note: The return value includes the first three digits (or less depending on the length
|
* <p>Note: The return value includes the first three digits (or less depending on the length
|
||||||
* of the string) of {@link ExifInterface#TAG_SUBSEC_TIME_DIGITIZED}.
|
* of the string) of {@link ExifInterfaceFork#TAG_SUBSEC_TIME_DIGITIZED}.
|
||||||
*
|
*
|
||||||
* @return null if digitized date time information is unavailable or invalid.
|
* @return null if digitized date time information is unavailable or invalid.
|
||||||
*/
|
*/
|
||||||
|
@ -5192,11 +5193,11 @@ public class ExifInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns parsed {@link ExifInterface#TAG_DATETIME_ORIGINAL} value as number of
|
* Returns parsed {@link ExifInterfaceFork#TAG_DATETIME_ORIGINAL} value as number of
|
||||||
* milliseconds since Jan. 1, 1970, midnight local time.
|
* milliseconds since Jan. 1, 1970, midnight local time.
|
||||||
*
|
*
|
||||||
* <p>Note: The return value includes the first three digits (or less depending on the length
|
* <p>Note: The return value includes the first three digits (or less depending on the length
|
||||||
* of the string) of {@link ExifInterface#TAG_SUBSEC_TIME_ORIGINAL}.
|
* of the string) of {@link ExifInterfaceFork#TAG_SUBSEC_TIME_ORIGINAL}.
|
||||||
*
|
*
|
||||||
* @return null if original date time information is unavailable or invalid.
|
* @return null if original date time information is unavailable or invalid.
|
||||||
*/
|
*/
|
||||||
|
@ -5910,18 +5911,18 @@ public class ExifInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rotation != null) {
|
if (rotation != null) {
|
||||||
int orientation = ExifInterface.ORIENTATION_NORMAL;
|
int orientation = ExifInterfaceFork.ORIENTATION_NORMAL;
|
||||||
|
|
||||||
// all rotation angles in CW
|
// all rotation angles in CW
|
||||||
switch (Integer.parseInt(rotation)) {
|
switch (Integer.parseInt(rotation)) {
|
||||||
case 90:
|
case 90:
|
||||||
orientation = ExifInterface.ORIENTATION_ROTATE_90;
|
orientation = ExifInterfaceFork.ORIENTATION_ROTATE_90;
|
||||||
break;
|
break;
|
||||||
case 180:
|
case 180:
|
||||||
orientation = ExifInterface.ORIENTATION_ROTATE_180;
|
orientation = ExifInterfaceFork.ORIENTATION_ROTATE_180;
|
||||||
break;
|
break;
|
||||||
case 270:
|
case 270:
|
||||||
orientation = ExifInterface.ORIENTATION_ROTATE_270;
|
orientation = ExifInterfaceFork.ORIENTATION_ROTATE_270;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6175,7 +6176,11 @@ public class ExifInterface {
|
||||||
// IEND marks the end of the image.
|
// IEND marks the end of the image.
|
||||||
break;
|
break;
|
||||||
} else if (Arrays.equals(type, PNG_CHUNK_TYPE_EXIF)) {
|
} else if (Arrays.equals(type, PNG_CHUNK_TYPE_EXIF)) {
|
||||||
// TODO: Need to handle potential OutOfMemoryError
|
// TLAD start
|
||||||
|
if (length > ATTRIBUTE_SIZE_DANGER_THRESHOLD) {
|
||||||
|
throw new IOException("dangerous exif chunk size=" + length);
|
||||||
|
}
|
||||||
|
// TLAD end
|
||||||
byte[] data = new byte[length];
|
byte[] data = new byte[length];
|
||||||
in.readFully(data);
|
in.readFully(data);
|
||||||
|
|
||||||
|
@ -6976,9 +6981,11 @@ public class ExifInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
final int bytesOffset = dataInputStream.position() + mOffsetToExifData;
|
final int bytesOffset = dataInputStream.position() + mOffsetToExifData;
|
||||||
if (byteCount > 0 && byteCount < ATTRIBUTE_SIZE_DANGER_THRESHOLD) {
|
// TLAD start
|
||||||
|
if (byteCount > ATTRIBUTE_SIZE_DANGER_THRESHOLD) {
|
||||||
throw new IOException("dangerous attribute size=" + byteCount);
|
throw new IOException("dangerous attribute size=" + byteCount);
|
||||||
}
|
}
|
||||||
|
// TLAD end
|
||||||
final byte[] bytes = new byte[(int) byteCount];
|
final byte[] bytes = new byte[(int) byteCount];
|
||||||
dataInputStream.readFully(bytes);
|
dataInputStream.readFully(bytes);
|
||||||
ExifAttribute attribute = new ExifAttribute(dataFormat, numberOfComponents,
|
ExifAttribute attribute = new ExifAttribute(dataFormat, numberOfComponents,
|
|
@ -32,10 +32,10 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
class ExifInterfaceUtils {
|
class ExifInterfaceUtilsFork {
|
||||||
private static final String TAG = "ExifInterfaceUtils";
|
private static final String TAG = "ExifInterfaceUtils";
|
||||||
|
|
||||||
private ExifInterfaceUtils() {
|
private ExifInterfaceUtilsFork() {
|
||||||
// Prevent instantiation
|
// Prevent instantiation
|
||||||
}
|
}
|
||||||
/**
|
/**
|
|
@ -10,7 +10,7 @@ pluginManagement {
|
||||||
|
|
||||||
settings.ext.kotlin_version = '1.9.24'
|
settings.ext.kotlin_version = '1.9.24'
|
||||||
settings.ext.ksp_version = "$kotlin_version-1.0.20"
|
settings.ext.ksp_version = "$kotlin_version-1.0.20"
|
||||||
settings.ext.agp_version = '8.5.0'
|
settings.ext.agp_version = '8.5.1'
|
||||||
|
|
||||||
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
|
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
|
|
@ -93,6 +93,8 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
|
||||||
_rawEntries.forEach((v) => v.dispose());
|
_rawEntries.forEach((v) => v.dispose());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set safeMode(bool enabled);
|
||||||
|
|
||||||
final EventBus _eventBus = EventBus();
|
final EventBus _eventBus = EventBus();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -23,6 +23,10 @@ class MediaStoreSource extends CollectionSource {
|
||||||
final Set<String> _changedUris = {};
|
final Set<String> _changedUris = {};
|
||||||
int? _lastGeneration;
|
int? _lastGeneration;
|
||||||
SourceInitializationState _initState = SourceInitializationState.none;
|
SourceInitializationState _initState = SourceInitializationState.none;
|
||||||
|
bool _safeMode = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
set safeMode(bool enabled) => _safeMode = enabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SourceInitializationState get initState => _initState;
|
SourceInitializationState get initState => _initState;
|
||||||
|
@ -46,7 +50,7 @@ class MediaStoreSource extends CollectionSource {
|
||||||
analysisController: analysisController,
|
analysisController: analysisController,
|
||||||
directory: directory,
|
directory: directory,
|
||||||
loadTopEntriesFirst: loadTopEntriesFirst,
|
loadTopEntriesFirst: loadTopEntriesFirst,
|
||||||
canAnalyze: canAnalyze,
|
canAnalyze: canAnalyze && _safeMode,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +179,7 @@ class MediaStoreSource extends CollectionSource {
|
||||||
pendingNewEntries.clear();
|
pendingNewEntries.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaStoreService.getEntries(knownDateByContentId, directory: directory).listen(
|
mediaStoreService.getEntries(_safeMode, knownDateByContentId, directory: directory).listen(
|
||||||
(entry) {
|
(entry) {
|
||||||
// when discovering modified entry with known content ID,
|
// when discovering modified entry with known content ID,
|
||||||
// reuse known entry ID to overwrite it while preserving favourites, etc.
|
// reuse known entry ID to overwrite it while preserving favourites, etc.
|
||||||
|
|
|
@ -15,7 +15,7 @@ abstract class MediaStoreService {
|
||||||
Future<int?> getGeneration();
|
Future<int?> getGeneration();
|
||||||
|
|
||||||
// knownEntries: map of contentId -> dateModifiedSecs
|
// knownEntries: map of contentId -> dateModifiedSecs
|
||||||
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory});
|
Stream<AvesEntry> getEntries(bool safe, Map<int?, int?> knownEntries, {String? directory});
|
||||||
|
|
||||||
// returns media URI
|
// returns media URI
|
||||||
Future<Uri?> scanFile(String path, String mimeType);
|
Future<Uri?> scanFile(String path, String mimeType);
|
||||||
|
@ -75,12 +75,13 @@ class PlatformMediaStoreService implements MediaStoreService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) {
|
Stream<AvesEntry> getEntries(bool safe, Map<int?, int?> knownEntries, {String? directory}) {
|
||||||
try {
|
try {
|
||||||
return _stream
|
return _stream
|
||||||
.receiveBroadcastStream(<String, dynamic>{
|
.receiveBroadcastStream(<String, dynamic>{
|
||||||
'knownEntries': knownEntries,
|
'knownEntries': knownEntries,
|
||||||
'directory': directory,
|
'directory': directory,
|
||||||
|
'safe': safe,
|
||||||
})
|
})
|
||||||
.where((event) => event is Map)
|
.where((event) => event is Map)
|
||||||
.map((event) => AvesEntry.fromMap(event as Map));
|
.map((event) => AvesEntry.fromMap(event as Map));
|
||||||
|
|
|
@ -203,10 +203,10 @@ class _HomePageState extends State<HomePage> {
|
||||||
unawaited(GlobalSearch.registerCallback());
|
unawaited(GlobalSearch.registerCallback());
|
||||||
unawaited(AnalysisService.registerCallback());
|
unawaited(AnalysisService.registerCallback());
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
|
source.safeMode = safeMode;
|
||||||
if (source.initState != SourceInitializationState.full) {
|
if (source.initState != SourceInitializationState.full) {
|
||||||
await source.init(
|
await source.init(
|
||||||
loadTopEntriesFirst: settings.homePage == HomePageSetting.collection && settings.homeCustomCollection.isEmpty,
|
loadTopEntriesFirst: settings.homePage == HomePageSetting.collection && settings.homeCustomCollection.isEmpty,
|
||||||
canAnalyze: !safeMode,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case AppMode.screenSaver:
|
case AppMode.screenSaver:
|
||||||
|
|
|
@ -4,7 +4,7 @@ version '1.0-SNAPSHOT'
|
||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
ext {
|
||||||
kotlin_version = '1.9.24'
|
kotlin_version = '1.9.24'
|
||||||
agp_version = '8.5.0'
|
agp_version = '8.5.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|
|
@ -33,7 +33,7 @@ class FakeMediaStoreService extends Fake implements MediaStoreService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) => Stream.fromIterable(entries);
|
Stream<AvesEntry> getEntries(bool safe, Map<int?, int?> knownEntries, {String? directory}) => Stream.fromIterable(entries);
|
||||||
|
|
||||||
static var _lastId = 1;
|
static var _lastId = 1;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue