diff options
| author | 2023-02-17 15:25:15 -0800 | |
|---|---|---|
| committer | 2023-03-02 12:18:32 -0800 | |
| commit | 1d9b80bad8984a6b2c4d6277e162de07ded7bd41 (patch) | |
| tree | b5378042eea18b5eb15eae5c37707f6bf173f177 /java/src/com | |
| parent | 37364423c143a737c37a10de6ede2f25fd476ad1 (diff) | |
Add image caching to ImagePreviewImageLoader
ScrollableImagePreviewView being a RecyclerView may reattache its
children multiple times and rely on the ImageLoader to implement any
image retrival optimizations.
Fix: 269797062
Test: manual test, unit tests
Change-Id: I256f4a78a677e939f717fee5dd82492ec572bc65
Diffstat (limited to 'java/src/com')
4 files changed, 51 insertions, 9 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 32b10f23..910eb885 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -85,6 +85,7 @@ import com.android.intentresolver.chooser.MultiDisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; import com.android.intentresolver.flags.FeatureFlagRepository; import com.android.intentresolver.flags.FeatureFlagRepositoryFactory; +import com.android.intentresolver.flags.Flags; import com.android.intentresolver.grid.ChooserGridAdapter; import com.android.intentresolver.grid.DirectShareViewHolder; import com.android.intentresolver.model.AbstractResolverComparator; @@ -1338,7 +1339,15 @@ public class ChooserActivity extends ResolverActivity implements @VisibleForTesting protected ImageLoader createPreviewImageLoader() { - return new ImagePreviewImageLoader(this, getLifecycle()); + final int cacheSize; + if (mFeatureFlagRepository.isEnabled(Flags.SHARESHEET_SCROLLABLE_IMAGE_PREVIEW)) { + float chooserWidth = getResources().getDimension(R.dimen.chooser_width); + float imageWidth = getResources().getDimension(R.dimen.chooser_preview_image_width); + cacheSize = (int) (Math.ceil(chooserWidth / imageWidth) + 2); + } else { + cacheSize = 3; + } + return new ImagePreviewImageLoader(this, getLifecycle(), cacheSize); } private void handleScroll(View view, int x, int y, int oldx, int oldy) { diff --git a/java/src/com/android/intentresolver/ChooserContentPreviewUi.java b/java/src/com/android/intentresolver/ChooserContentPreviewUi.java index aa147853..60ea0122 100644 --- a/java/src/com/android/intentresolver/ChooserContentPreviewUi.java +++ b/java/src/com/android/intentresolver/ChooserContentPreviewUi.java @@ -17,6 +17,7 @@ package com.android.intentresolver; import static android.content.ContentProvider.getUserIdFromUri; + import static java.lang.annotation.RetentionPolicy.SOURCE; import android.animation.ObjectAnimator; @@ -413,6 +414,7 @@ public final class ChooserContentPreviewUi { actionFactory); imagePreview.setTransitionElementStatusCallback(transitionElementStatusCallback); imagePreview.setImages(imageUris, imageLoader); + imageLoader.prePopulate(imageUris); return contentPreviewLayout; } diff --git a/java/src/com/android/intentresolver/ImageLoader.kt b/java/src/com/android/intentresolver/ImageLoader.kt index 13b1dd9c..0ed8b122 100644 --- a/java/src/com/android/intentresolver/ImageLoader.kt +++ b/java/src/com/android/intentresolver/ImageLoader.kt @@ -22,4 +22,5 @@ import java.util.function.Consumer interface ImageLoader : suspend (Uri) -> Bitmap? { fun loadImage(uri: Uri, callback: Consumer<Bitmap?>) + fun prePopulate(uris: List<Uri>) } diff --git a/java/src/com/android/intentresolver/ImagePreviewImageLoader.kt b/java/src/com/android/intentresolver/ImagePreviewImageLoader.kt index 40081c87..7b6651a2 100644 --- a/java/src/com/android/intentresolver/ImagePreviewImageLoader.kt +++ b/java/src/com/android/intentresolver/ImagePreviewImageLoader.kt @@ -20,21 +20,34 @@ import android.content.Context import android.graphics.Bitmap import android.net.Uri import android.util.Size +import androidx.annotation.GuardedBy +import androidx.annotation.VisibleForTesting +import androidx.collection.LruCache import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.util.function.Consumer -internal class ImagePreviewImageLoader @JvmOverloads constructor( +@VisibleForTesting +class ImagePreviewImageLoader @JvmOverloads constructor( private val context: Context, private val lifecycle: Lifecycle, + cacheSize: Int, private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) : ImageLoader { + private val thumbnailSize: Size = + context.resources.getDimensionPixelSize(R.dimen.chooser_preview_image_max_dimen).let { + Size(it, it) + } + + @GuardedBy("self") + private val cache = LruCache<Uri, CompletableDeferred<Bitmap?>>(cacheSize) + override suspend fun invoke(uri: Uri): Bitmap? = loadImageAsync(uri) override fun loadImage(uri: Uri, callback: Consumer<Bitmap?>) { @@ -46,12 +59,29 @@ internal class ImagePreviewImageLoader @JvmOverloads constructor( } } - private suspend fun loadImageAsync(uri: Uri): Bitmap? { - val size = context.resources.getDimensionPixelSize(R.dimen.chooser_preview_image_max_dimen) - return withContext(dispatcher) { - runCatching { - context.contentResolver.loadThumbnail(uri, Size(size, size), null) - }.getOrNull() + override fun prePopulate(uris: List<Uri>) { + uris.asSequence().take(cache.maxSize()).forEach { uri -> + lifecycle.coroutineScope.launch { + loadImageAsync(uri) + } } } + + private suspend fun loadImageAsync(uri: Uri): Bitmap? { + return synchronized(cache) { + cache.get(uri) ?: CompletableDeferred<Bitmap?>().also { result -> + cache.put(uri, result) + lifecycle.coroutineScope.launch(dispatcher) { + result.loadBitmap(uri) + } + } + }.await() + } + + private fun CompletableDeferred<Bitmap?>.loadBitmap(uri: Uri) { + val bitmap = runCatching { + context.contentResolver.loadThumbnail(uri, thumbnailSize, null) + }.getOrNull() + complete(bitmap) + } } |