Fix system ANR when loading avatars for system UI.
This commit is contained in:
parent
95d7d26f11
commit
fc8385113f
1 changed files with 14 additions and 92 deletions
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue