Fix system ANR when loading avatars for system UI.

This commit is contained in:
Cody Henthorne 2023-11-10 15:19:40 -05:00
parent 95d7d26f11
commit fc8385113f

View file

@ -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> = 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)
compress(Bitmap.CompressFormat.PNG, 100, output)
}
output.flush()
if (VERBOSE) Log.i(TAG, "Writing to pipe done:${recipient.id}")
}
}
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
}
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]
}
}