From fc8385113f8eb55dd698f055a57f98c3ecaeb225 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Fri, 10 Nov 2023 15:19:40 -0500 Subject: [PATCH] Fix system ANR when loading avatars for system UI. --- .../securesms/providers/AvatarProvider.kt | 106 +++--------------- 1 file changed, 14 insertions(+), 92 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt index 59726ef34c..e66e2279d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt @@ -7,21 +7,12 @@ package org.thoughtcrime.securesms.providers import android.content.ContentUris import android.content.ContentValues -import android.content.Context import android.content.Intent import android.content.UriMatcher import android.database.Cursor import android.graphics.Bitmap import android.net.Uri -import android.os.Build -import android.os.Handler -import android.os.HandlerThread -import android.os.MemoryFile import android.os.ParcelFileDescriptor -import android.os.ProxyFileDescriptorCallback -import androidx.annotation.RequiresApi -import org.signal.core.util.StreamUtil -import org.signal.core.util.ThreadUtil import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.BuildConfig @@ -33,10 +24,6 @@ import org.thoughtcrime.securesms.service.KeyCachingService import org.thoughtcrime.securesms.util.AvatarUtil import org.thoughtcrime.securesms.util.DrawableUtil import org.thoughtcrime.securesms.util.MediaUtil -import org.thoughtcrime.securesms.util.MemoryFileUtil -import org.thoughtcrime.securesms.util.ServiceUtil -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream import java.io.File import java.io.FileNotFoundException import java.io.IOException @@ -90,14 +77,10 @@ class AvatarProvider : BaseContentProvider() { if (VERBOSE) Log.i(TAG, "Loading avatar.") try { val recipient = getRecipientId(uri)?.let { Recipient.resolved(it) } ?: return null - return if (Build.VERSION.SDK_INT >= 26) { - getParcelStreamProxyForAvatar(recipient) - } else { - getParcelStreamForAvatar(recipient) - } + return getParcelFileDescriptorForAvatar(recipient) } catch (ioe: IOException) { Log.w(TAG, ioe) - throw FileNotFoundException("Error opening file") + throw FileNotFoundException("Error opening file: " + ioe.message) } } @@ -178,82 +161,21 @@ class AvatarProvider : BaseContentProvider() { return recipientId } - @RequiresApi(26) - private fun getParcelStreamProxyForAvatar(recipient: Recipient): ParcelFileDescriptor { - val storageManager = requireNotNull(ServiceUtil.getStorageManager(context!!)) - val handlerThread = SignalExecutors.getAndStartHandlerThread("avatarservice-proxy", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD) - val handler = Handler(handlerThread.looper) + private fun getParcelFileDescriptorForAvatar(recipient: Recipient): ParcelFileDescriptor { + val pipe: Array = ParcelFileDescriptor.createPipe() - val parcelFileDescriptor = storageManager.openProxyFileDescriptor( - ParcelFileDescriptor.MODE_READ_ONLY, - ProxyCallback(context!!.applicationContext, recipient, handlerThread), - handler - ) + SignalExecutors.UNBOUNDED.execute { + ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]).use { output -> + if (VERBOSE) Log.i(TAG, "Writing to pipe:${recipient.id}") - if (VERBOSE) Log.i(TAG, "${recipient.id}:createdProxy") - return parcelFileDescriptor - } - - private fun getParcelStreamForAvatar(recipient: Recipient): ParcelFileDescriptor { - val outputStream = ByteArrayOutputStream() - AvatarUtil.getBitmapForNotification(context!!, recipient, DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE).apply { - compress(Bitmap.CompressFormat.PNG, 100, outputStream) - } - - val memoryFile = MemoryFile("${recipient.id}-imf", outputStream.size()) - - val memoryFileOutputStream = memoryFile.outputStream - StreamUtil.copy(ByteArrayInputStream(outputStream.toByteArray()), memoryFileOutputStream) - StreamUtil.close(memoryFileOutputStream) - - return MemoryFileUtil.getParcelFileDescriptor(memoryFile) - } - - @RequiresApi(26) - private class ProxyCallback( - private val context: Context, - private val recipient: Recipient, - private val handlerThread: HandlerThread - ) : ProxyFileDescriptorCallback() { - - private var memoryFile: MemoryFile? = null - - override fun onGetSize(): Long { - if (VERBOSE) Log.i(TAG, "${recipient.id}:onGetSize:${Thread.currentThread().name}:${hashCode()}") - ensureResourceLoaded() - return memoryFile!!.length().toLong() - } - - override fun onRead(offset: Long, size: Int, data: ByteArray?): Int { - ensureResourceLoaded() - val memoryFileSnapshot = memoryFile - return memoryFileSnapshot!!.readBytes(data, offset.toInt(), 0, size.coerceAtMost((memoryFileSnapshot.length() - offset).toInt())) - } - - override fun onRelease() { - if (VERBOSE) Log.i(TAG, "${recipient.id}:onRelease") - memoryFile = null - handlerThread.quitSafely() - } - - private fun ensureResourceLoaded() { - if (memoryFile != null) { - return + AvatarUtil.getBitmapForNotification(context!!, recipient, DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE).apply { + compress(Bitmap.CompressFormat.PNG, 100, output) + } + output.flush() + if (VERBOSE) Log.i(TAG, "Writing to pipe done:${recipient.id}") } - - if (VERBOSE) Log.i(TAG, "Reading ${recipient.id} icon into RAM.") - - val outputStream = ByteArrayOutputStream() - val avatarBitmap = AvatarUtil.getBitmapForNotification(context, recipient, DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE) - avatarBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) - - if (VERBOSE) Log.i(TAG, "Writing ${recipient.id} icon to MemoryFile") - - memoryFile = MemoryFile("${recipient.id}-imf", outputStream.size()) - - val memoryFileOutputStream = memoryFile!!.outputStream - StreamUtil.copy(ByteArrayInputStream(outputStream.toByteArray()), memoryFileOutputStream) - StreamUtil.close(memoryFileOutputStream) } + + return pipe[0] } }