summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt7
-rw-r--r--java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java5
-rw-r--r--java/src/com/android/intentresolver/contentpreview/ImageLoader.kt14
-rw-r--r--java/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoader.kt7
-rw-r--r--java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java9
-rw-r--r--java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java17
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt13
-rw-r--r--java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt21
-rw-r--r--java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt42
-rw-r--r--tests/shared/src/com/android/intentresolver/FakeImageLoader.kt12
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt36
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt48
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt75
-rw-r--r--tests/unit/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt28
14 files changed, 219 insertions, 115 deletions
diff --git a/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt
index c9deec1b..f60f550e 100644
--- a/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt
+++ b/java/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoader.kt
@@ -19,6 +19,7 @@ package com.android.intentresolver.contentpreview
import android.graphics.Bitmap
import android.net.Uri
import android.util.Log
+import android.util.Size
import androidx.core.util.lruCache
import com.android.intentresolver.inject.Background
import com.android.intentresolver.inject.ViewModelOwned
@@ -72,11 +73,11 @@ constructor(
}
)
- override fun prePopulate(uris: List<Uri>) {
- uris.take(cache.maxSize()).map { cache[it] }
+ override fun prePopulate(uriSizePairs: List<Pair<Uri, Size>>) {
+ uriSizePairs.take(cache.maxSize()).map { cache[it.first] }
}
- override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? {
+ override suspend fun invoke(uri: Uri, size: Size, caching: Boolean): Bitmap? {
return if (caching) {
loadCachedImage(uri)
} else {
diff --git a/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java
index b50f5bc8..30161cfb 100644
--- a/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java
+++ b/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java
@@ -23,6 +23,7 @@ import android.content.res.Resources;
import android.net.Uri;
import android.text.util.Linkify;
import android.util.PluralsMessageFormatter;
+import android.util.Size;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -68,6 +69,7 @@ class FilesPlusTextContentPreviewUi extends ContentPreviewUi {
private Uri mFirstFilePreviewUri;
private boolean mAllImages;
private boolean mAllVideos;
+ private int mPreviewSize;
// TODO(b/285309527): make this a flag
private static final boolean SHOW_TOGGLE_CHECKMARK = false;
@@ -109,6 +111,7 @@ class FilesPlusTextContentPreviewUi extends ContentPreviewUi {
LayoutInflater layoutInflater,
ViewGroup parent,
View headlineViewParent) {
+ mPreviewSize = resources.getDimensionPixelSize(R.dimen.width_text_image_preview_size);
return displayInternal(layoutInflater, parent, headlineViewParent);
}
@@ -164,12 +167,12 @@ class FilesPlusTextContentPreviewUi extends ContentPreviewUi {
private void updateUiWithMetadata(ViewGroup contentPreviewView, View headlineView) {
prepareTextPreview(contentPreviewView, headlineView, mActionFactory);
updateHeadline(headlineView, mFileCount, mAllImages, mAllVideos);
-
ImageView imagePreview = mContentPreviewView.requireViewById(R.id.image_view);
if (mIsSingleImage && mFirstFilePreviewUri != null) {
mImageLoader.loadImage(
mScope,
mFirstFilePreviewUri,
+ new Size(mPreviewSize, mPreviewSize),
bitmap -> {
if (bitmap == null) {
imagePreview.setVisibility(View.GONE);
diff --git a/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt
index b4d03ac9..ac34f552 100644
--- a/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt
+++ b/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt
@@ -18,23 +18,25 @@ package com.android.intentresolver.contentpreview
import android.graphics.Bitmap
import android.net.Uri
+import android.util.Size
import java.util.function.Consumer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
/** A content preview image loader. */
-interface ImageLoader : suspend (Uri) -> Bitmap?, suspend (Uri, Boolean) -> Bitmap? {
+interface ImageLoader : suspend (Uri, Size) -> Bitmap?, suspend (Uri, Size, Boolean) -> Bitmap? {
/**
* Load preview image asynchronously; caching is allowed.
*
* @param uri content URI
+ * @param size target bitmap size
* @param callback a callback that will be invoked with the loaded image or null if loading has
* failed.
*/
- fun loadImage(callerScope: CoroutineScope, uri: Uri, callback: Consumer<Bitmap?>) {
+ fun loadImage(callerScope: CoroutineScope, uri: Uri, size: Size, callback: Consumer<Bitmap?>) {
callerScope.launch {
- val bitmap = invoke(uri)
+ val bitmap = invoke(uri, size)
if (isActive) {
callback.accept(bitmap)
}
@@ -42,13 +44,13 @@ interface ImageLoader : suspend (Uri) -> Bitmap?, suspend (Uri, Boolean) -> Bitm
}
/** Prepopulate the image loader cache. */
- fun prePopulate(uris: List<Uri>)
+ fun prePopulate(uriSizePairs: List<Pair<Uri, Size>>)
/** Returns a bitmap for the given URI if it's already cached, otherwise null */
fun getCachedBitmap(uri: Uri): Bitmap? = null
/** Load preview image; caching is allowed. */
- override suspend fun invoke(uri: Uri) = invoke(uri, true)
+ override suspend fun invoke(uri: Uri, size: Size) = invoke(uri, size, true)
/**
* Load preview image.
@@ -56,5 +58,5 @@ interface ImageLoader : suspend (Uri) -> Bitmap?, suspend (Uri, Boolean) -> Bitm
* @param uri content URI
* @param caching indicates if the loaded image could be cached.
*/
- override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap?
+ override suspend fun invoke(uri: Uri, size: Size, caching: Boolean): Bitmap?
}
diff --git a/java/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoader.kt
index 7cf9a8c9..379bdb37 100644
--- a/java/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoader.kt
+++ b/java/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoader.kt
@@ -98,10 +98,11 @@ constructor(
@GuardedBy("lock") private val cache = LruCache<Uri, RequestRecord>(cacheSize)
@GuardedBy("lock") private val runningRequests = HashMap<Uri, RequestRecord>()
- override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? = loadImageAsync(uri, caching)
+ override suspend fun invoke(uri: Uri, size: Size, caching: Boolean): Bitmap? =
+ loadImageAsync(uri, caching)
- override fun prePopulate(uris: List<Uri>) {
- uris.asSequence().take(cache.maxSize()).forEach { uri ->
+ override fun prePopulate(uriSizePairs: List<Pair<Uri, Size>>) {
+ uriSizePairs.asSequence().take(cache.maxSize()).forEach { (uri, _) ->
scope.launch { loadImageAsync(uri, caching = true) }
}
}
diff --git a/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java
index ae7ddcd9..b12eb8cf 100644
--- a/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java
+++ b/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java
@@ -22,6 +22,7 @@ import android.content.res.Resources;
import android.net.Uri;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
+import android.util.Size;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -50,6 +51,7 @@ class TextContentPreviewUi extends ContentPreviewUi {
private final ChooserContentPreviewUi.ActionFactory mActionFactory;
private final HeadlineGenerator mHeadlineGenerator;
private final ContentTypeHint mContentTypeHint;
+ private int mPreviewSize;
TextContentPreviewUi(
CoroutineScope scope,
@@ -83,6 +85,7 @@ class TextContentPreviewUi extends ContentPreviewUi {
LayoutInflater layoutInflater,
ViewGroup parent,
View headlineViewParent) {
+ mPreviewSize = resources.getDimensionPixelSize(R.dimen.width_text_image_preview_size);
return displayInternal(layoutInflater, parent, headlineViewParent);
}
@@ -119,7 +122,7 @@ class TextContentPreviewUi extends ContentPreviewUi {
previewTitleView.setText(mPreviewTitle);
}
- ImageView previewThumbnailView = contentPreviewLayout.findViewById(
+ final ImageView previewThumbnailView = contentPreviewLayout.requireViewById(
com.android.internal.R.id.content_preview_thumbnail);
if (!isOwnedByCurrentUser(mPreviewThumbnail)) {
previewThumbnailView.setVisibility(View.GONE);
@@ -127,9 +130,9 @@ class TextContentPreviewUi extends ContentPreviewUi {
mImageLoader.loadImage(
mScope,
mPreviewThumbnail,
+ new Size(mPreviewSize, mPreviewSize),
(bitmap) -> updateViewWithImage(
- contentPreviewLayout.findViewById(
- com.android.internal.R.id.content_preview_thumbnail),
+ previewThumbnailView,
bitmap));
}
diff --git a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java
index 88311016..7de988c4 100644
--- a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java
+++ b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java
@@ -20,6 +20,7 @@ import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTE
import android.content.res.Resources;
import android.util.Log;
+import android.util.Size;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -31,6 +32,8 @@ import com.android.intentresolver.widget.ActionRow;
import com.android.intentresolver.widget.ImagePreviewView.TransitionElementStatusCallback;
import com.android.intentresolver.widget.ScrollableImagePreviewView;
+import kotlin.Pair;
+
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.flow.Flow;
@@ -55,6 +58,7 @@ class UnifiedContentPreviewUi extends ContentPreviewUi {
@Nullable
private ViewGroup mContentPreviewView;
private View mHeadlineView;
+ private int mPreviewSize;
UnifiedContentPreviewUi(
CoroutineScope scope,
@@ -93,14 +97,18 @@ class UnifiedContentPreviewUi extends ContentPreviewUi {
LayoutInflater layoutInflater,
ViewGroup parent,
View headlineViewParent) {
+ mPreviewSize = resources.getDimensionPixelSize(R.dimen.chooser_preview_image_max_dimen);
return displayInternal(layoutInflater, parent, headlineViewParent);
}
private void setFiles(List<FileInfo> files) {
- mImageLoader.prePopulate(files.stream()
- .map(FileInfo::getPreviewUri)
- .filter(Objects::nonNull)
- .toList());
+ Size previewSize = new Size(mPreviewSize, mPreviewSize);
+ mImageLoader.prePopulate(
+ files.stream()
+ .map(FileInfo::getPreviewUri)
+ .filter(Objects::nonNull)
+ .map((uri -> new Pair<>(uri, previewSize)))
+ .toList());
mFiles = files;
if (mContentPreviewView != null) {
updatePreviewWithFiles(mContentPreviewView, mHeadlineView, files);
@@ -121,6 +129,7 @@ class UnifiedContentPreviewUi extends ContentPreviewUi {
ScrollableImagePreviewView imagePreview =
mContentPreviewView.requireViewById(R.id.scrollable_image_preview);
+ imagePreview.setPreviewHeight(mPreviewSize);
imagePreview.setImageLoader(mImageLoader);
imagePreview.setOnNoPreviewCallback(() -> imagePreview.setVisibility(View.GONE));
imagePreview.setTransitionElementStatusCallback(mTransitionElementStatusCallback);
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt
index 3c3381a2..9ac36a87 100644
--- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt
+++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/composable/ShareouselComposable.kt
@@ -111,6 +111,7 @@ private fun PreviewCarousel(
prefetchStrategy = remember { ShareouselLazyListPrefetchStrategy() }
)
var maxAspectRatio by remember { mutableStateOf(0f) }
+ var viewportHeight by remember { mutableStateOf(0) }
val horizontalPadding = 16.dp
Box(
@@ -130,9 +131,8 @@ private fun PreviewCarousel(
MAX_ASPECT_RATIO
)
}
- if (maxAspectRatio != aspectRatio) {
- maxAspectRatio = aspectRatio
- }
+ maxAspectRatio = aspectRatio
+ viewportHeight = placeable.height
layout(placeable.width, placeable.height) { placeable.place(0, 0) }
},
) {
@@ -170,7 +170,12 @@ private fun PreviewCarousel(
}
ShareouselCard(
- viewModel.preview(model, previewIndex, rememberCoroutineScope()),
+ viewModel.preview(
+ model,
+ viewportHeight,
+ previewIndex,
+ rememberCoroutineScope()
+ ),
maxAspectRatio,
)
}
diff --git a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt
index d0b89860..f1e65f73 100644
--- a/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt
+++ b/java/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModel.kt
@@ -15,6 +15,7 @@
*/
package com.android.intentresolver.contentpreview.payloadtoggle.ui.viewmodel
+import android.util.Size
import com.android.intentresolver.contentpreview.CachingImagePreviewImageLoader
import com.android.intentresolver.contentpreview.HeadlineGenerator
import com.android.intentresolver.contentpreview.ImageLoader
@@ -57,7 +58,9 @@ data class ShareouselViewModel(
val actions: Flow<List<ActionChipViewModel>>,
/** Creates a [ShareouselPreviewViewModel] for a [PreviewModel] present in [previews]. */
val preview:
- (key: PreviewModel, index: Int?, scope: CoroutineScope) -> ShareouselPreviewViewModel,
+ (
+ key: PreviewModel, previewHeight: Int, index: Int?, scope: CoroutineScope
+ ) -> ShareouselPreviewViewModel,
)
@Module
@@ -114,7 +117,7 @@ interface ShareouselViewModelModule {
}
}
},
- preview = { key, index, previewScope ->
+ preview = { key, previewHeight, index, previewScope ->
keySet.value?.maybeLoad(index)
val previewInteractor = interactor.preview(key)
val contentType =
@@ -130,9 +133,19 @@ interface ShareouselViewModelModule {
ShareouselPreviewViewModel(
bitmapLoadState =
flow {
+ val previewWidth =
+ if (key.aspectRatio > 0) {
+ previewHeight.toFloat() / key.aspectRatio
+ } else {
+ previewHeight
+ }
+ .toInt()
emit(
- key.previewUri?.let { ValueUpdate.Value(imageLoader(it)) }
- ?: ValueUpdate.Absent
+ key.previewUri?.let {
+ ValueUpdate.Value(
+ imageLoader(it, Size(previewWidth, previewHeight))
+ )
+ } ?: ValueUpdate.Absent
)
}
.stateIn(previewScope, SharingStarted.Eagerly, initialBitmapValue),
diff --git a/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt b/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt
index 7fe16091..c706e3ee 100644
--- a/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt
+++ b/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt
@@ -22,6 +22,7 @@ import android.graphics.Rect
import android.net.Uri
import android.util.AttributeSet
import android.util.PluralsMessageFormatter
+import android.util.Size
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
@@ -60,11 +61,13 @@ private const val MIN_ASPECT_RATIO_STRING = "2:5"
private const val MAX_ASPECT_RATIO = 2.5f
private const val MAX_ASPECT_RATIO_STRING = "5:2"
-private typealias CachingImageLoader = suspend (Uri, Boolean) -> Bitmap?
+private typealias CachingImageLoader = suspend (Uri, Size, Boolean) -> Bitmap?
class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
constructor(context: Context) : this(context, null)
+
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
+
constructor(
context: Context,
attrs: AttributeSet?,
@@ -121,12 +124,19 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
* A hint about the maximum width this view can grow to, this helps to optimize preview loading.
*/
var maxWidthHint: Int = -1
+
private var requestedHeight: Int = 0
private var isMeasured = false
private var maxAspectRatio = MAX_ASPECT_RATIO
private var maxAspectRatioString = MAX_ASPECT_RATIO_STRING
private var outerSpacing: Int = 0
+ var previewHeight: Int
+ get() = previewAdapter.previewHeight
+ set(value) {
+ previewAdapter.previewHeight = value
+ }
+
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
super.onMeasure(widthSpec, heightSpec)
if (!isMeasured) {
@@ -198,6 +208,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
BatchPreviewLoader(
previewAdapter.imageLoader ?: error("Image loader is not set"),
previews,
+ Size(previewHeight, previewHeight),
totalItemCount,
onUpdate = previewAdapter::addPreviews,
onCompletion = {
@@ -303,11 +314,19 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
private var isLoading = false
private val hasOtherItem
get() = previews.size < totalItemCount
+
val hasPreviews: Boolean
get() = previews.isNotEmpty()
var transitionStatusElementCallback: TransitionElementStatusCallback? = null
+ private var previewSize: Size = Size(0, 0)
+ var previewHeight: Int
+ get() = previewSize.height
+ set(value) {
+ previewSize = Size(value, value)
+ }
+
fun reset(totalItemCount: Int) {
firstImagePos = -1
previews.clear()
@@ -387,6 +406,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
vh.bind(
previews[position],
imageLoader ?: error("ImageLoader is missing"),
+ previewSize,
fadeInDurationMs,
isSharedTransitionElement = position == firstImagePos,
previewReadyCallback =
@@ -438,6 +458,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
fun bind(
preview: Preview,
imageLoader: CachingImageLoader,
+ previewSize: Size,
fadeInDurationMs: Long,
isSharedTransitionElement: Boolean,
previewReadyCallback: ((String) -> Unit)?
@@ -477,7 +498,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
}
}
resetScope().launch {
- loadImage(preview, imageLoader)
+ loadImage(preview, previewSize, imageLoader)
if (preview.type == PreviewType.Image && previewReadyCallback != null) {
image.waitForPreDraw()
previewReadyCallback(TRANSITION_NAME)
@@ -487,12 +508,16 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
}
}
- private suspend fun loadImage(preview: Preview, imageLoader: CachingImageLoader) {
+ private suspend fun loadImage(
+ preview: Preview,
+ previewSize: Size,
+ imageLoader: CachingImageLoader,
+ ) {
val bitmap =
runCatching {
// it's expected for all loading/caching optimizations to be implemented by
// the loader
- imageLoader(preview.uri, true)
+ imageLoader(preview.uri, previewSize, true)
}
.getOrNull()
image.setImageBitmap(bitmap)
@@ -507,6 +532,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
setAnimationListener(
object : AnimationListener {
override fun onAnimationStart(animation: Animation?) = Unit
+
override fun onAnimationRepeat(animation: Animation?) = Unit
override fun onAnimationEnd(animation: Animation?) {
@@ -551,6 +577,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
private class LoadingItemViewHolder(view: View) : ViewHolder(view) {
fun bind() = Unit
+
override fun unbind() = Unit
}
@@ -638,6 +665,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
class BatchPreviewLoader(
private val imageLoader: CachingImageLoader,
private val previews: Flow<Preview>,
+ private val previewSize: Size,
val totalItemCount: Int,
private val onUpdate: (List<Preview>) -> Unit,
private val onCompletion: () -> Unit,
@@ -701,10 +729,10 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView {
// imagine is one of the first images never loads so we never
// fill the initial viewport and does not show the previews at
// all.
- imageLoader(preview.uri, isFirstBlock)?.let { bitmap ->
+ imageLoader(preview.uri, previewSize, isFirstBlock)?.let {
+ bitmap ->
previewSizeUpdater(preview, bitmap.width, bitmap.height)
- }
- ?: 0
+ } ?: 0
}
.getOrDefault(0)
diff --git a/tests/shared/src/com/android/intentresolver/FakeImageLoader.kt b/tests/shared/src/com/android/intentresolver/FakeImageLoader.kt
index c57ea78b..76eb5e0d 100644
--- a/tests/shared/src/com/android/intentresolver/FakeImageLoader.kt
+++ b/tests/shared/src/com/android/intentresolver/FakeImageLoader.kt
@@ -18,6 +18,7 @@ package com.android.intentresolver
import android.graphics.Bitmap
import android.net.Uri
+import android.util.Size
import com.android.intentresolver.contentpreview.ImageLoader
import java.util.function.Consumer
import kotlinx.coroutines.CoroutineScope
@@ -25,13 +26,18 @@ import kotlinx.coroutines.CoroutineScope
class FakeImageLoader(initialBitmaps: Map<Uri, Bitmap> = emptyMap()) : ImageLoader {
private val bitmaps = HashMap<Uri, Bitmap>().apply { putAll(initialBitmaps) }
- override fun loadImage(callerScope: CoroutineScope, uri: Uri, callback: Consumer<Bitmap?>) {
+ override fun loadImage(
+ callerScope: CoroutineScope,
+ uri: Uri,
+ size: Size,
+ callback: Consumer<Bitmap?>,
+ ) {
callback.accept(bitmaps[uri])
}
- override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? = bitmaps[uri]
+ override suspend fun invoke(uri: Uri, size: Size, caching: Boolean): Bitmap? = bitmaps[uri]
- override fun prePopulate(uris: List<Uri>) = Unit
+ override fun prePopulate(uriSizePairs: List<Pair<Uri, Size>>) = Unit
fun setBitmap(uri: Uri, bitmap: Bitmap) {
bitmaps[uri] = bitmap
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt
index 331f9f64..d5a569aa 100644
--- a/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt
+++ b/tests/unit/src/com/android/intentresolver/contentpreview/CachingImagePreviewImageLoaderTest.kt
@@ -18,6 +18,7 @@ package com.android.intentresolver.contentpreview
import android.graphics.Bitmap
import android.net.Uri
+import android.util.Size
import com.google.common.truth.Truth.assertThat
import kotlin.math.ceil
import kotlin.math.roundToInt
@@ -43,6 +44,7 @@ class CachingImagePreviewImageLoaderTest {
testJobTime * ceil((testCacheSize).toFloat() / testMaxConcurrency.toFloat()).roundToInt()
private val testUris =
List(5) { Uri.fromParts("TestScheme$it", "TestSsp$it", "TestFragment$it") }
+ private val previewSize = Size(500, 500)
private val testTimeToLoadAllUris =
testJobTime * ceil((testUris.size).toFloat() / testMaxConcurrency.toFloat()).roundToInt()
private val testBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8)
@@ -72,7 +74,7 @@ class CachingImagePreviewImageLoaderTest {
var result: Bitmap? = null
// Act
- imageLoader.loadImage(testScope, testUris[0]) { result = it }
+ imageLoader.loadImage(testScope, testUris[0], previewSize) { result = it }
advanceTimeBy(testJobTime)
runCurrent()
@@ -85,14 +87,14 @@ class CachingImagePreviewImageLoaderTest {
fun loadImage_cached_usesCachedValue() =
testScope.runTest {
// Arrange
- imageLoader.loadImage(testScope, testUris[0]) {}
+ imageLoader.loadImage(testScope, testUris[0], previewSize) {}
advanceTimeBy(testJobTime)
runCurrent()
fakeThumbnailLoader.invokeCalls.clear()
var result: Bitmap? = null
// Act
- imageLoader.loadImage(testScope, testUris[0]) { result = it }
+ imageLoader.loadImage(testScope, testUris[0], previewSize) { result = it }
advanceTimeBy(testJobTime)
runCurrent()
@@ -112,7 +114,7 @@ class CachingImagePreviewImageLoaderTest {
var result: Bitmap? = testBitmap
// Act
- imageLoader.loadImage(testScope, testUris[0]) { result = it }
+ imageLoader.loadImage(testScope, testUris[0], previewSize) { result = it }
advanceTimeBy(testJobTime)
runCurrent()
@@ -130,7 +132,7 @@ class CachingImagePreviewImageLoaderTest {
// Act
testUris.take(testMaxConcurrency + 1).forEach { uri ->
- imageLoader.loadImage(testScope, uri) { results.add(it) }
+ imageLoader.loadImage(testScope, uri, previewSize) { results.add(it) }
}
// Assert
@@ -153,10 +155,10 @@ class CachingImagePreviewImageLoaderTest {
assertThat(testUris.size).isGreaterThan(testCacheSize)
// Act
- imageLoader.loadImage(testScope, testUris[0]) { results[0] = it }
+ imageLoader.loadImage(testScope, testUris[0], previewSize) { results[0] = it }
runCurrent()
testUris.indices.drop(1).take(testCacheSize).forEach { i ->
- imageLoader.loadImage(testScope, testUris[i]) { results[i] = it }
+ imageLoader.loadImage(testScope, testUris[i], previewSize) { results[i] = it }
}
advanceTimeBy(testTimeToFillCache)
runCurrent()
@@ -179,7 +181,7 @@ class CachingImagePreviewImageLoaderTest {
assertThat(fullCacheUris).hasSize(testCacheSize)
// Act
- imageLoader.prePopulate(fullCacheUris)
+ imageLoader.prePopulate(fullCacheUris.map { it to previewSize })
advanceTimeBy(testTimeToFillCache)
runCurrent()
@@ -188,7 +190,7 @@ class CachingImagePreviewImageLoaderTest {
// Act
fakeThumbnailLoader.invokeCalls.clear()
- imageLoader.prePopulate(fullCacheUris)
+ imageLoader.prePopulate(fullCacheUris.map { it to previewSize })
advanceTimeBy(testTimeToFillCache)
runCurrent()
@@ -203,7 +205,7 @@ class CachingImagePreviewImageLoaderTest {
assertThat(testUris.size).isGreaterThan(testCacheSize)
// Act
- imageLoader.prePopulate(testUris)
+ imageLoader.prePopulate(testUris.map { it to previewSize })
advanceTimeBy(testTimeToLoadAllUris)
runCurrent()
@@ -213,7 +215,7 @@ class CachingImagePreviewImageLoaderTest {
// Act
fakeThumbnailLoader.invokeCalls.clear()
- imageLoader.prePopulate(testUris)
+ imageLoader.prePopulate(testUris.map { it to previewSize })
advanceTimeBy(testTimeToLoadAllUris)
runCurrent()
@@ -229,7 +231,7 @@ class CachingImagePreviewImageLoaderTest {
assertThat(unfilledCacheUris.size).isLessThan(testCacheSize)
// Act
- imageLoader.prePopulate(unfilledCacheUris)
+ imageLoader.prePopulate(unfilledCacheUris.map { it to previewSize })
advanceTimeBy(testJobTime)
runCurrent()
@@ -238,7 +240,7 @@ class CachingImagePreviewImageLoaderTest {
// Act
fakeThumbnailLoader.invokeCalls.clear()
- imageLoader.prePopulate(unfilledCacheUris)
+ imageLoader.prePopulate(unfilledCacheUris.map { it to previewSize })
advanceTimeBy(testJobTime)
runCurrent()
@@ -252,8 +254,8 @@ class CachingImagePreviewImageLoaderTest {
// Arrange
// Act
- imageLoader.invoke(testUris[0], caching = false)
- imageLoader.invoke(testUris[0], caching = false)
+ imageLoader.invoke(testUris[0], previewSize, caching = false)
+ imageLoader.invoke(testUris[0], previewSize, caching = false)
advanceTimeBy(testJobTime)
runCurrent()
@@ -267,8 +269,8 @@ class CachingImagePreviewImageLoaderTest {
// Arrange
// Act
- imageLoader.invoke(testUris[0], caching = true)
- imageLoader.invoke(testUris[0], caching = true)
+ imageLoader.invoke(testUris[0], previewSize, caching = true)
+ imageLoader.invoke(testUris[0], previewSize, caching = true)
advanceTimeBy(testJobTime)
runCurrent()
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt
index 3a45e2f6..d78e6665 100644
--- a/tests/unit/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt
+++ b/tests/unit/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt
@@ -77,24 +77,25 @@ class ImagePreviewImageLoaderTest {
contentResolver,
cacheSize = 1,
)
+ private val previewSize = Size(500, 500)
@Test
fun prePopulate_cachesImagesUpToTheCacheSize() =
scope.runTest {
- testSubject.prePopulate(listOf(uriOne, uriTwo))
+ testSubject.prePopulate(listOf(uriOne to previewSize, uriTwo to previewSize))
verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null)
verify(contentResolver, never()).loadThumbnail(uriTwo, imageSize, null)
- testSubject(uriOne)
+ testSubject(uriOne, previewSize)
verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null)
}
@Test
fun invoke_returnCachedImageWhenCalledTwice() =
scope.runTest {
- testSubject(uriOne)
- testSubject(uriOne)
+ testSubject(uriOne, previewSize)
+ testSubject(uriOne, previewSize)
verify(contentResolver, times(1)).loadThumbnail(any(), any(), anyOrNull())
}
@@ -102,8 +103,8 @@ class ImagePreviewImageLoaderTest {
@Test
fun invoke_whenInstructed_doesNotCache() =
scope.runTest {
- testSubject(uriOne, false)
- testSubject(uriOne, false)
+ testSubject(uriOne, previewSize, false)
+ testSubject(uriOne, previewSize, false)
verify(contentResolver, times(2)).loadThumbnail(any(), any(), anyOrNull())
}
@@ -120,8 +121,8 @@ class ImagePreviewImageLoaderTest {
cacheSize = 1,
)
coroutineScope {
- launch(start = UNDISPATCHED) { testSubject(uriOne, false) }
- launch(start = UNDISPATCHED) { testSubject(uriOne, false) }
+ launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) }
+ launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) }
scheduler.advanceUntilIdle()
}
@@ -131,10 +132,10 @@ class ImagePreviewImageLoaderTest {
@Test
fun invoke_oldRecordsEvictedFromTheCache() =
scope.runTest {
- testSubject(uriOne)
- testSubject(uriTwo)
- testSubject(uriTwo)
- testSubject(uriOne)
+ testSubject(uriOne, previewSize)
+ testSubject(uriTwo, previewSize)
+ testSubject(uriTwo, previewSize)
+ testSubject(uriOne, previewSize)
verify(contentResolver, times(2)).loadThumbnail(uriOne, imageSize, null)
verify(contentResolver, times(1)).loadThumbnail(uriTwo, imageSize, null)
@@ -144,8 +145,8 @@ class ImagePreviewImageLoaderTest {
fun invoke_doNotCacheNulls() =
scope.runTest {
whenever(contentResolver.loadThumbnail(any(), any(), anyOrNull())).thenReturn(null)
- testSubject(uriOne)
- testSubject(uriOne)
+ testSubject(uriOne, previewSize)
+ testSubject(uriOne, previewSize)
verify(contentResolver, times(2)).loadThumbnail(uriOne, imageSize, null)
}
@@ -162,7 +163,7 @@ class ImagePreviewImageLoaderTest {
cacheSize = 1,
)
imageLoaderScope.cancel()
- testSubject(uriOne)
+ testSubject(uriOne, previewSize)
}
@Test(expected = CancellationException::class)
@@ -178,7 +179,8 @@ class ImagePreviewImageLoaderTest {
cacheSize = 1,
)
coroutineScope {
- val deferred = async(start = UNDISPATCHED) { testSubject(uriOne, false) }
+ val deferred =
+ async(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) }
imageLoaderScope.cancel()
scheduler.advanceUntilIdle()
deferred.await()
@@ -198,11 +200,11 @@ class ImagePreviewImageLoaderTest {
cacheSize = 1,
)
coroutineScope {
- launch(start = UNDISPATCHED) { testSubject(uriOne, false) }
- launch(start = UNDISPATCHED) { testSubject(uriOne, true) }
+ launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) }
+ launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, true) }
scheduler.advanceUntilIdle()
}
- testSubject(uriOne, true)
+ testSubject(uriOne, previewSize, true)
verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null)
}
@@ -243,7 +245,7 @@ class ImagePreviewImageLoaderTest {
cacheSize = 1,
testSemaphore,
)
- testSubject(uriOne, false)
+ testSubject(uriOne, previewSize, false)
verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null)
assertThat(acquireCount.get()).isEqualTo(1)
@@ -281,7 +283,7 @@ class ImagePreviewImageLoaderTest {
cacheSize = 1,
testSemaphore,
)
- launch(start = UNDISPATCHED) { testSubject(uriOne, false) }
+ launch(start = UNDISPATCHED) { testSubject(uriOne, previewSize, false) }
verify(contentResolver, never()).loadThumbnail(any(), any(), anyOrNull())
@@ -324,7 +326,9 @@ class ImagePreviewImageLoaderTest {
)
coroutineScope {
repeat(requestCount) {
- launch { testSubject(Uri.parse("content://org.pkg.app/image-$it.png")) }
+ launch {
+ testSubject(Uri.parse("content://org.pkg.app/image-$it.png"), previewSize)
+ }
}
yield()
// wait for all requests to be dispatched
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt
index bb67e084..1047d145 100644
--- a/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt
+++ b/tests/unit/src/com/android/intentresolver/contentpreview/payloadtoggle/ui/viewmodel/ShareouselViewModelTest.kt
@@ -76,23 +76,25 @@ class ShareouselViewModelTest {
scope = viewModelScope,
)
}
+ private val previewHeight = 500
@Test
fun headline_images() = runTest {
assertThat(shareouselViewModel.headline.first()).isEqualTo("FILES: 1")
previewSelectionsRepository.selections.value =
listOf(
- PreviewModel(
- uri = Uri.fromParts("scheme", "ssp", "fragment"),
- mimeType = "image/png",
- order = 0,
- ),
- PreviewModel(
- uri = Uri.fromParts("scheme1", "ssp1", "fragment1"),
- mimeType = "image/jpeg",
- order = 1,
+ PreviewModel(
+ uri = Uri.fromParts("scheme", "ssp", "fragment"),
+ mimeType = "image/png",
+ order = 0,
+ ),
+ PreviewModel(
+ uri = Uri.fromParts("scheme1", "ssp1", "fragment1"),
+ mimeType = "image/jpeg",
+ order = 1,
+ )
)
- ).associateBy { it.uri }
+ .associateBy { it.uri }
runCurrent()
assertThat(shareouselViewModel.headline.first()).isEqualTo("IMAGES: 2")
}
@@ -101,17 +103,18 @@ class ShareouselViewModelTest {
fun headline_videos() = runTest {
previewSelectionsRepository.selections.value =
listOf(
- PreviewModel(
- uri = Uri.fromParts("scheme", "ssp", "fragment"),
- mimeType = "video/mpeg",
- order = 0,
- ),
- PreviewModel(
- uri = Uri.fromParts("scheme1", "ssp1", "fragment1"),
- mimeType = "video/mpeg",
- order = 1,
+ PreviewModel(
+ uri = Uri.fromParts("scheme", "ssp", "fragment"),
+ mimeType = "video/mpeg",
+ order = 0,
+ ),
+ PreviewModel(
+ uri = Uri.fromParts("scheme1", "ssp1", "fragment1"),
+ mimeType = "video/mpeg",
+ order = 1,
+ )
)
- ).associateBy { it.uri }
+ .associateBy { it.uri }
runCurrent()
assertThat(shareouselViewModel.headline.first()).isEqualTo("VIDEOS: 2")
}
@@ -120,17 +123,18 @@ class ShareouselViewModelTest {
fun headline_mixed() = runTest {
previewSelectionsRepository.selections.value =
listOf(
- PreviewModel(
- uri = Uri.fromParts("scheme", "ssp", "fragment"),
- mimeType = "image/jpeg",
- order = 0,
- ),
- PreviewModel(
- uri = Uri.fromParts("scheme1", "ssp1", "fragment1"),
- mimeType = "video/mpeg",
- order = 1,
+ PreviewModel(
+ uri = Uri.fromParts("scheme", "ssp", "fragment"),
+ mimeType = "image/jpeg",
+ order = 0,
+ ),
+ PreviewModel(
+ uri = Uri.fromParts("scheme1", "ssp1", "fragment1"),
+ mimeType = "video/mpeg",
+ order = 1,
+ )
)
- ).associateBy { it.uri }
+ .associateBy { it.uri }
runCurrent()
assertThat(shareouselViewModel.headline.first()).isEqualTo("FILES: 2")
}
@@ -194,6 +198,7 @@ class ShareouselViewModelTest {
mimeType = "video/mpeg",
order = 0,
),
+ previewHeight,
/* index = */ 1,
viewModelScope,
)
@@ -245,6 +250,7 @@ class ShareouselViewModelTest {
mimeType = "video/mpeg",
order = 1,
),
+ previewHeight,
/* index = */ 1,
viewModelScope,
)
@@ -308,10 +314,11 @@ class ShareouselViewModelTest {
this.targetIntentModifier = targetIntentModifier
previewSelectionsRepository.selections.value =
PreviewModel(
- uri = Uri.fromParts("scheme", "ssp", "fragment"),
- mimeType = null,
- order = 0,
- ).let { mapOf(it.uri to it) }
+ uri = Uri.fromParts("scheme", "ssp", "fragment"),
+ mimeType = null,
+ order = 0,
+ )
+ .let { mapOf(it.uri to it) }
payloadToggleImageLoader =
FakeImageLoader(
initialBitmaps =
diff --git a/tests/unit/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt b/tests/unit/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt
index 4f4223c0..b1e8593d 100644
--- a/tests/unit/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt
+++ b/tests/unit/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt
@@ -18,6 +18,7 @@ package com.android.intentresolver.widget
import android.graphics.Bitmap
import android.net.Uri
+import android.util.Size
import com.android.intentresolver.captureMany
import com.android.intentresolver.mock
import com.android.intentresolver.widget.ScrollableImagePreviewView.BatchPreviewLoader
@@ -49,6 +50,7 @@ class BatchPreviewLoaderTest {
private val testScope = CoroutineScope(dispatcher)
private val onCompletion = mock<() -> Unit>()
private val onUpdate = mock<(List<Preview>) -> Unit>()
+ private val previewSize = Size(500, 500)
@Before
fun setup() {
@@ -71,6 +73,7 @@ class BatchPreviewLoaderTest {
BatchPreviewLoader(
imageLoader,
previews(uriOne, uriTwo),
+ previewSize,
totalItemCount = 2,
onUpdate,
onCompletion
@@ -94,6 +97,7 @@ class BatchPreviewLoaderTest {
BatchPreviewLoader(
imageLoader,
previews(uriOne, uriTwo, uriThree),
+ previewSize,
totalItemCount = 3,
onUpdate,
onCompletion
@@ -122,7 +126,14 @@ class BatchPreviewLoaderTest {
}
imageLoader.setUriLoadingOrder(*loadingOrder)
val testSubject =
- BatchPreviewLoader(imageLoader, previews(*uris), uris.size, onUpdate, onCompletion)
+ BatchPreviewLoader(
+ imageLoader,
+ previews(*uris),
+ previewSize,
+ uris.size,
+ onUpdate,
+ onCompletion
+ )
testSubject.loadAspectRatios(200) { _, _, _ -> 100 }
dispatcher.scheduler.advanceUntilIdle()
@@ -151,7 +162,14 @@ class BatchPreviewLoaderTest {
val expectedUris = Array(uris.size / 2) { createUri(it * 2 + 1) }
imageLoader.setUriLoadingOrder(*loadingOrder)
val testSubject =
- BatchPreviewLoader(imageLoader, previews(*uris), uris.size, onUpdate, onCompletion)
+ BatchPreviewLoader(
+ imageLoader,
+ previews(*uris),
+ previewSize,
+ uris.size,
+ onUpdate,
+ onCompletion
+ )
testSubject.loadAspectRatios(200) { _, _, _ -> 100 }
dispatcher.scheduler.advanceUntilIdle()
@@ -166,7 +184,9 @@ class BatchPreviewLoaderTest {
private fun createUri(idx: Int): Uri = Uri.parse("content://org.pkg.app/image-$idx.png")
private fun fail(uri: Uri) = uri to false
+
private fun succeed(uri: Uri) = uri to true
+
private fun previews(vararg uris: Uri) =
uris
.fold(ArrayList<Preview>(uris.size)) { acc, uri ->
@@ -175,7 +195,7 @@ class BatchPreviewLoaderTest {
.asFlow()
}
-private class TestImageLoader(scope: CoroutineScope) : suspend (Uri, Boolean) -> Bitmap? {
+private class TestImageLoader(scope: CoroutineScope) : suspend (Uri, Size, Boolean) -> Bitmap? {
private val loadingOrder = ArrayDeque<Pair<Uri, Boolean>>()
private val pendingRequests = LinkedHashMap<Uri, CompletableDeferred<Bitmap?>>()
private val flow = MutableSharedFlow<Unit>(replay = 1)
@@ -203,7 +223,7 @@ private class TestImageLoader(scope: CoroutineScope) : suspend (Uri, Boolean) ->
loadingOrder.addAll(uris)
}
- override suspend fun invoke(uri: Uri, cache: Boolean): Bitmap? {
+ override suspend fun invoke(uri: Uri, size: Size, cache: Boolean): Bitmap? {
val deferred = pendingRequests.getOrPut(uri) { CompletableDeferred() }
flow.tryEmit(Unit)
return deferred.await()