mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 06:55:41 +00:00
Merge develop into the wildcard mime type branch
Develop gained resolveImageMimeType, which reads the image header so a share that declares a broad type is encoded as the format it really is. Resolving the wildcard first would hand that helper a concrete image/jpeg and make it return early, so a transparent PNG shared as image/* would be flattened and an animated GIF would be recompressed. The header is now read from the type the sender declared and the default subtype is applied to whatever comes back, so the sniffing still runs and an undecodable file no longer keeps a wildcard. A non-image shared as image/* is therefore sent as image/jpeg rather than image/*, which is the whole point of this branch, so the test covering that case now expects the default subtype.
This commit is contained in:
+18
-4
@@ -63,10 +63,10 @@ class AndroidMediaPreProcessor(
|
||||
/**
|
||||
* Used for calculating `inSampleSize` for bitmaps.
|
||||
*
|
||||
* *Note*: Ideally, this should result in images of up to (but not included) 1280x1280 being sent. However, images with very different width and height
|
||||
* *Note*: Ideally, this should result in images of up to (but not included) 2560x2560 being sent. However, images with very different width and height
|
||||
* values may surpass this limit. (i.e.: an image of `480x3000px` would have `inSampleSize=1` and be sent as is).
|
||||
*/
|
||||
private const val IMAGE_SCALE_REF_SIZE = 640
|
||||
private const val IMAGE_SCALE_REF_SIZE = 1280
|
||||
|
||||
private val notCompressibleImageTypes = listOf(MimeTypes.Gif, MimeTypes.WebP)
|
||||
}
|
||||
@@ -87,8 +87,9 @@ class AndroidMediaPreProcessor(
|
||||
val result = when {
|
||||
resolvedMimeType == MimeTypes.Svg -> processSvgImage(uri, resolvedMimeType)
|
||||
resolvedMimeType.isMimeTypeImage() -> {
|
||||
val shouldBeCompressed = mediaOptimizationConfig.compressImages && resolvedMimeType !in notCompressibleImageTypes
|
||||
processImage(uri, resolvedMimeType, shouldBeCompressed)
|
||||
val imageMimeType = resolveImageMimeType(uri, mimeType).ensureDefaultSubtype()
|
||||
val shouldBeCompressed = mediaOptimizationConfig.compressImages && imageMimeType !in notCompressibleImageTypes
|
||||
processImage(uri, imageMimeType, shouldBeCompressed)
|
||||
}
|
||||
resolvedMimeType.isMimeTypeVideo() -> processVideo(uri, resolvedMimeType, mediaOptimizationConfig.videoCompressionPreset)
|
||||
resolvedMimeType.isMimeTypeAudio() -> processAudio(uri, resolvedMimeType)
|
||||
@@ -157,6 +158,19 @@ class AndroidMediaPreProcessor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The declared mime type can be broader than the image actually is - a wildcard from a share intent, or a mislabelled file - and the encoder is picked
|
||||
* from it. Re-encoding a transparent image as JPEG flattens its alpha channel to black, so resolve the real format from the image header first.
|
||||
*/
|
||||
private fun resolveImageMimeType(uri: Uri, declaredMimeType: String): String {
|
||||
if (declaredMimeType == MimeTypes.Png || declaredMimeType == MimeTypes.Jpeg) return declaredMimeType
|
||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
tryOrNull {
|
||||
contentResolver.openInputStream(uri)?.use { BitmapFactory.decodeStream(it, null, options) }
|
||||
}
|
||||
return options.outMimeType ?: declaredMimeType
|
||||
}
|
||||
|
||||
private suspend fun processImage(uri: Uri, mimeType: String, shouldBeCompressed: Boolean): MediaUploadInfo {
|
||||
Timber.d("Processing image ${uri.path.orEmpty().hash()}")
|
||||
|
||||
|
||||
+53
-7
@@ -82,9 +82,9 @@ class AndroidMediaPreProcessorTest : RobolectricTest() {
|
||||
private suspend fun TestScope.process(
|
||||
asset: Asset,
|
||||
mediaOptimizationConfig: MediaOptimizationConfig,
|
||||
mimeType: String = asset.mimeType,
|
||||
sdkIntVersion: Int = Build.VERSION_CODES.P,
|
||||
deleteOriginal: Boolean = false,
|
||||
mimeType: String = asset.mimeType,
|
||||
): MediaUploadInfo {
|
||||
val context = InstrumentationRegistry.getInstrumentation().context
|
||||
val deleteCallback = lambdaRecorder<Uri?, Unit> {}
|
||||
@@ -106,6 +106,52 @@ class AndroidMediaPreProcessorTest : RobolectricTest() {
|
||||
return data
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a png shared with a wildcard mime type is still encoded as a png`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImagePng,
|
||||
mimeType = MimeTypes.Images,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
sdkIntVersion = Build.VERSION_CODES.Q,
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
assertThat(info.imageInfo.mimetype).isEqualTo(MimeTypes.Png)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a gif shared with a wildcard mime type is recognised and left uncompressed`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetAnimatedGif,
|
||||
mimeType = MimeTypes.Images,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
sdkIntVersion = Build.VERSION_CODES.Q,
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
assertThat(info.imageInfo.mimetype).isEqualTo(MimeTypes.Gif)
|
||||
assertThat(info.imageInfo.size).isEqualTo(assetAnimatedGif.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a file that is not an image falls back to the default subtype of the wildcard it was shared with`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetText,
|
||||
mimeType = MimeTypes.Images,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = false,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
sdkIntVersion = Build.VERSION_CODES.Q,
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
assertThat(info.imageInfo.mimetype).isEqualTo(MimeTypes.Jpeg)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Ignore now that min API for enterprise is 33")
|
||||
fun `test processing png`() = runTest {
|
||||
@@ -223,10 +269,10 @@ class AndroidMediaPreProcessorTest : RobolectricTest() {
|
||||
assertThat(info.thumbnailFile).isNotNull()
|
||||
assertThat(info.imageInfo).isEqualTo(
|
||||
ImageInfo(
|
||||
height = 979,
|
||||
width = 3006,
|
||||
height = 1958,
|
||||
width = 6012,
|
||||
mimetype = MimeTypes.Jpeg,
|
||||
size = 84_845,
|
||||
size = 682_492,
|
||||
ThumbnailInfo(height = 244, width = 751, mimetype = MimeTypes.Jpeg, size = 7_178),
|
||||
thumbnailSource = null,
|
||||
blurhash = "K07gBzX=j_D4xZjoaSe,s:"
|
||||
@@ -248,10 +294,10 @@ class AndroidMediaPreProcessorTest : RobolectricTest() {
|
||||
assertThat(info.thumbnailFile).isNull()
|
||||
assertThat(info.imageInfo).isEqualTo(
|
||||
ImageInfo(
|
||||
height = 979,
|
||||
width = 3_006,
|
||||
height = 1_958,
|
||||
width = 6_012,
|
||||
mimetype = MimeTypes.Jpeg,
|
||||
size = 84_845,
|
||||
size = 682_492,
|
||||
thumbnailInfo = null,
|
||||
thumbnailSource = null,
|
||||
blurhash = null,
|
||||
|
||||
Reference in New Issue
Block a user