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.ContentUris
import android.content.ContentValues import android.content.ContentValues
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.UriMatcher import android.content.UriMatcher
import android.database.Cursor import android.database.Cursor
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri 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.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.concurrent.SignalExecutors
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.BuildConfig 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.AvatarUtil
import org.thoughtcrime.securesms.util.DrawableUtil import org.thoughtcrime.securesms.util.DrawableUtil
import org.thoughtcrime.securesms.util.MediaUtil 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.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
@ -90,14 +77,10 @@ class AvatarProvider : BaseContentProvider() {
if (VERBOSE) Log.i(TAG, "Loading avatar.") if (VERBOSE) Log.i(TAG, "Loading avatar.")
try { try {
val recipient = getRecipientId(uri)?.let { Recipient.resolved(it) } ?: return null val recipient = getRecipientId(uri)?.let { Recipient.resolved(it) } ?: return null
return if (Build.VERSION.SDK_INT >= 26) { return getParcelFileDescriptorForAvatar(recipient)
getParcelStreamProxyForAvatar(recipient)
} else {
getParcelStreamForAvatar(recipient)
}
} catch (ioe: IOException) { } catch (ioe: IOException) {
Log.w(TAG, ioe) 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 return recipientId
} }
@RequiresApi(26) private fun getParcelFileDescriptorForAvatar(recipient: Recipient): ParcelFileDescriptor {
private fun getParcelStreamProxyForAvatar(recipient: Recipient): ParcelFileDescriptor { val pipe: Array<ParcelFileDescriptor> = ParcelFileDescriptor.createPipe()
val storageManager = requireNotNull(ServiceUtil.getStorageManager(context!!))
val handlerThread = SignalExecutors.getAndStartHandlerThread("avatarservice-proxy", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD)
val handler = Handler(handlerThread.looper)
val parcelFileDescriptor = storageManager.openProxyFileDescriptor( SignalExecutors.UNBOUNDED.execute {
ParcelFileDescriptor.MODE_READ_ONLY, ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]).use { output ->
ProxyCallback(context!!.applicationContext, recipient, handlerThread), if (VERBOSE) Log.i(TAG, "Writing to pipe:${recipient.id}")
handler
)
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 { 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()) return pipe[0]
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)
}
} }
} }