Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9d6da91b authored by Jeff DeCew's avatar Jeff DeCew
Browse files

Fix stalled background thread bug

This change forces the smart reply action icon load to happen on a new thread (using a thread pool to avoid bursts of thread allocations in nominal reinflate situations) and has a 500ms timeout.

Fixes: 283789325
Test: Running Proof of Concept app, blocking, and posting lots of notifications
Change-Id: Ided53dfd0c696fb5895d8b55806cb0219d8a0624
parent f0e2b49e
Loading
Loading
Loading
Loading
+64 −6
Original line number Diff line number Diff line
@@ -22,6 +22,13 @@ import android.app.PendingIntent
import android.app.RemoteInput
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Bundle
import android.os.SystemClock
@@ -48,7 +55,13 @@ import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedA
import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions
import com.android.systemui.statusbar.policy.SmartReplyView.SmartButtonType
import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies
import java.util.concurrent.FutureTask
import java.util.concurrent.SynchronousQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.system.measureTimeMillis


/** Returns whether we should show the smart reply view and its smart suggestions. */
fun shouldShowSmartReplyView(
@@ -281,6 +294,51 @@ interface SmartActionInflater {
    ): Button
}

private const val ICON_TASK_TIMEOUT_MS = 500L
private val iconTaskThreadPool = ThreadPoolExecutor(0, 25, 1, TimeUnit.MINUTES, SynchronousQueue())

private fun loadIconDrawableWithTimeout(
    icon: Icon,
    packageContext: Context,
    targetSize: Int,
): Drawable? {
    if (icon.type != Icon.TYPE_URI && icon.type != Icon.TYPE_URI_ADAPTIVE_BITMAP) {
        return icon.loadDrawable(packageContext)
    }
    val bitmapTask = FutureTask {
        val bitmap: Bitmap?
        val durationMillis = measureTimeMillis {
            val source = ImageDecoder.createSource(packageContext.contentResolver, icon.uri)
            bitmap = ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
                decoder.setTargetSize(targetSize, targetSize)
                decoder.allocator = ImageDecoder.ALLOCATOR_DEFAULT
            }
        }
        if (durationMillis > ICON_TASK_TIMEOUT_MS) {
            Log.w(TAG, "Loading $icon took ${durationMillis / 1000f} sec")
        }
        checkNotNull(bitmap) { "ImageDecoder.decodeBitmap() returned null" }
    }
    val bitmap = runCatching {
        iconTaskThreadPool.execute(bitmapTask)
        bitmapTask.get(ICON_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS)
    }.getOrElse { ex ->
        Log.e(TAG, "Failed to load $icon: $ex")
        bitmapTask.cancel(true)
        return null
    }
    // TODO(b/288561520): rewrite Icon so that we don't need to duplicate this logic
    val bitmapDrawable = BitmapDrawable(packageContext.resources, bitmap)
    val result = if (icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP)
        AdaptiveIconDrawable(null, bitmapDrawable) else bitmapDrawable
    if (icon.hasTint()) {
        result.mutate()
        result.setTintList(icon.tintList)
        result.setTintBlendMode(icon.tintBlendMode)
    }
    return result
}

/* internal */ class SmartActionInflaterImpl @Inject constructor(
    private val constants: SmartReplyConstants,
    private val activityStarter: ActivityStarter,
@@ -304,12 +362,12 @@ interface SmartActionInflater {

                // We received the Icon from the application - so use the Context of the application to
                // reference icon resources.
                val iconDrawable = action.getIcon().loadDrawable(packageContext)
                        .apply {
                            val newIconSize: Int = context.resources.getDimensionPixelSize(
                                    R.dimen.smart_action_button_icon_size)
                            setBounds(0, 0, newIconSize, newIconSize)
                        }
                val newIconSize = context.resources
                    .getDimensionPixelSize(R.dimen.smart_action_button_icon_size)
                val iconDrawable =
                    loadIconDrawableWithTimeout(action.getIcon(), packageContext, newIconSize)
                        ?: GradientDrawable()
                iconDrawable.setBounds(0, 0, newIconSize, newIconSize)
                // Add the action icon to the Smart Action button.
                setCompoundDrawablesRelative(iconDrawable, null, null, null)