diff options
author | Ricki Hirner <hirner@bitfire.at> | 2022-03-20 21:22:31 +0300 |
---|---|---|
committer | Ricki Hirner <hirner@bitfire.at> | 2022-03-20 22:05:53 +0300 |
commit | 76d56fed49c7d1dd08fd3d63f02aa7a11898e074 (patch) | |
tree | 6e67f685f8c95a004fa8bec2fa37422e6bee0c89 | |
parent | 1af1eba9d22f777b5c3a8fa047ab1df8772d5f18 (diff) |
Contact photos: always read them as JPEG
-rw-r--r-- | src/androidTest/java/at/bitfire/vcard4android/contactrow/PhotoHandlerTest.kt | 23 | ||||
-rw-r--r-- | src/androidTest/resources/small.jpg | bin | 0 -> 1614 bytes | |||
-rw-r--r-- | src/androidTest/resources/small.png | bin | 0 -> 757 bytes | |||
-rw-r--r-- | src/main/java/at/bitfire/vcard4android/contactrow/PhotoHandler.kt | 62 |
4 files changed, 80 insertions, 5 deletions
diff --git a/src/androidTest/java/at/bitfire/vcard4android/contactrow/PhotoHandlerTest.kt b/src/androidTest/java/at/bitfire/vcard4android/contactrow/PhotoHandlerTest.kt index fea2217..004995e 100644 --- a/src/androidTest/java/at/bitfire/vcard4android/contactrow/PhotoHandlerTest.kt +++ b/src/androidTest/java/at/bitfire/vcard4android/contactrow/PhotoHandlerTest.kt @@ -23,7 +23,7 @@ import org.junit.Assert.* import org.junit.BeforeClass import org.junit.ClassRule import org.junit.Test -import kotlin.random.Random +import java.util.* class PhotoHandlerTest { @@ -57,6 +57,25 @@ class PhotoHandlerTest { @Test + fun testConvertToJpeg_Invalid() { + val blob = ByteArray(1024) { it.toByte() } + assertNull(PhotoHandler.convertToJpeg(blob, 75)) + } + + @Test + fun testConvertToJpeg_Jpeg() { + val blob = IOUtils.resourceToByteArray("/small.jpg") + assertArrayEquals(blob, PhotoHandler.convertToJpeg(blob, 75)) + } + + @Test + fun testConvertToJpeg_Png() { + val blob = IOUtils.resourceToByteArray("/small.png") + assertFalse(Arrays.equals(blob, PhotoHandler.convertToJpeg(blob, 75))) + } + + + @Test fun testPhoto_Empty() { val contact = Contact() PhotoHandler(null).handle(ContentValues().apply { @@ -67,7 +86,7 @@ class PhotoHandlerTest { @Test fun testPhoto_Blob() { - val blob = ByteArray(1024) { Random.nextInt().toByte() } + val blob = IOUtils.resourceToByteArray("/small.jpg") val contact = Contact() PhotoHandler(null).handle(ContentValues().apply { put(Photo.PHOTO, blob) diff --git a/src/androidTest/resources/small.jpg b/src/androidTest/resources/small.jpg Binary files differnew file mode 100644 index 0000000..683de52 --- /dev/null +++ b/src/androidTest/resources/small.jpg diff --git a/src/androidTest/resources/small.png b/src/androidTest/resources/small.png Binary files differnew file mode 100644 index 0000000..68f895a --- /dev/null +++ b/src/androidTest/resources/small.png diff --git a/src/main/java/at/bitfire/vcard4android/contactrow/PhotoHandler.kt b/src/main/java/at/bitfire/vcard4android/contactrow/PhotoHandler.kt index de17724..d807f85 100644 --- a/src/main/java/at/bitfire/vcard4android/contactrow/PhotoHandler.kt +++ b/src/main/java/at/bitfire/vcard4android/contactrow/PhotoHandler.kt @@ -7,27 +7,76 @@ package at.bitfire.vcard4android.contactrow import android.content.ContentProviderClient import android.content.ContentUris import android.content.ContentValues +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.provider.ContactsContract import android.provider.ContactsContract.CommonDataKinds.Photo import at.bitfire.vcard4android.Constants import at.bitfire.vcard4android.Contact +import org.apache.commons.io.FileUtils import org.apache.commons.io.IOUtils +import java.io.ByteArrayOutputStream import java.io.IOException import java.util.logging.Level class PhotoHandler(val provider: ContentProviderClient?): DataRowHandler() { + companion object { + + /** + * Converts non-JPEG images to JPEG (compression: 75). + * Does nothing if image is already in JPEG format. + * + * @param photo image in format that can be read by [BitmapFactory] + * @param quality JPEG quality (ignored when [photo] is already in JPEG format) + * @return same image in JPEG format or null if image couldn't be converted + */ + fun convertToJpeg(photo: ByteArray, quality: Int): ByteArray? { + val opts = BitmapFactory.Options() + opts.inJustDecodeBounds = true + BitmapFactory.decodeByteArray(photo, 0, photo.size, opts) + if (opts.outMimeType == "image/jpeg") + // image is already a JPEG + return photo + + // decode image + val bmp = BitmapFactory.decodeByteArray(photo, 0, photo.size) + if (bmp == null) + return null + + // compress as JPEG + val result = ByteArrayOutputStream() + if (bmp.compress(Bitmap.CompressFormat.JPEG, quality, result)) + return result.toByteArray() + + return null + } + + } + + override fun forMimeType() = Photo.CONTENT_ITEM_TYPE override fun handle(values: ContentValues, contact: Contact) { super.handle(values, contact) + /* JPEG qualities are taken from + https://android.googlesource.com/platform/packages/providers/ContactsProvider.git/+/refs/heads/android12-release/src/com/android/providers/contacts/PhotoProcessor.java + + Compression for display photos: 75 + Compression for thumbnails that don't have a full-size photo: 95 + */ + values.getAsLong(Photo.PHOTO_FILE_ID)?.let { photoId -> val photoUri = ContentUris.withAppendedId(ContactsContract.DisplayPhoto.CONTENT_URI, photoId) try { provider?.openAssetFile(photoUri, "r")?.let { file -> file.createInputStream().use { - contact.photo = IOUtils.toByteArray(it) + // Samsung Android 12 bug: they return a PNG image with MIME type image/jpeg + convertToJpeg(IOUtils.toByteArray(it), 75)?.let { jpeg -> + Constants.log.log(Level.FINE, "Got high-res contact photo (${FileUtils.byteCountToDisplaySize(jpeg.size.toLong())})") + contact.photo = jpeg + } } } } catch(e: IOException) { @@ -35,8 +84,15 @@ class PhotoHandler(val provider: ContentProviderClient?): DataRowHandler() { } } - if (contact.photo == null) - contact.photo = values.getAsByteArray(Photo.PHOTO) + if (contact.photo == null) { + values.getAsByteArray(Photo.PHOTO)?.let { thumbnail -> + // Samsung Android 12 bug: even the thumbnail is a PNG image + convertToJpeg(thumbnail, 95)?.let { jpeg -> + Constants.log.log(Level.FINE, "Got contact photo thumbnail (${FileUtils.byteCountToDisplaySize(jpeg.size.toLong())})") + contact.photo = jpeg + } + } + } } }
\ No newline at end of file |