diff options
Diffstat (limited to 'java')
15 files changed, 230 insertions, 71 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 404d6da3..917a4e5d 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -84,6 +84,7 @@ import com.android.intentresolver.chooser.MultiDisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; import com.android.intentresolver.contentpreview.ChooserContentPreviewUi; import com.android.intentresolver.contentpreview.HeadlineGeneratorImpl; +import com.android.intentresolver.contentpreview.ImageLoader; import com.android.intentresolver.flags.FeatureFlagRepository; import com.android.intentresolver.flags.FeatureFlagRepositoryFactory; import com.android.intentresolver.grid.ChooserGridAdapter; @@ -1287,9 +1288,9 @@ public class ChooserActivity extends ResolverActivity implements protected ImageLoader createPreviewImageLoader() { final int cacheSize; float chooserWidth = getResources().getDimension(R.dimen.chooser_width); - // imageWidth = imagePreviewHeight / minAspectRatio (see ScrollableImagePreviewView) + // imageWidth = imagePreviewHeight * minAspectRatio (see ScrollableImagePreviewView) float imageWidth = - getResources().getDimension(R.dimen.chooser_preview_image_height_tall) * 5 / 2; + getResources().getDimension(R.dimen.chooser_preview_image_height_tall) * 2 / 5; cacheSize = (int) (Math.ceil(chooserWidth / imageWidth) + 2); return new ImagePreviewImageLoader(this, getLifecycle(), cacheSize); } diff --git a/java/src/com/android/intentresolver/ImageLoader.kt b/java/src/com/android/intentresolver/ImageLoader.kt deleted file mode 100644 index 0ed8b122..00000000 --- a/java/src/com/android/intentresolver/ImageLoader.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver - -import android.graphics.Bitmap -import android.net.Uri -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 9650403e..c97efdd1 100644 --- a/java/src/com/android/intentresolver/ImagePreviewImageLoader.kt +++ b/java/src/com/android/intentresolver/ImagePreviewImageLoader.kt @@ -26,8 +26,11 @@ import androidx.annotation.VisibleForTesting import androidx.collection.LruCache import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope +import com.android.intentresolver.contentpreview.ImageLoader +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -35,6 +38,10 @@ import java.util.function.Consumer private const val TAG = "ImagePreviewImageLoader" +/** + * Implements preview image loading for the content preview UI. Provides requests deduplication and + * image caching. + */ @VisibleForTesting class ImagePreviewImageLoader @JvmOverloads constructor( private val context: Context, @@ -48,14 +55,17 @@ class ImagePreviewImageLoader @JvmOverloads constructor( Size(it, it) } - @GuardedBy("self") - private val cache = LruCache<Uri, CompletableDeferred<Bitmap?>>(cacheSize) + private val lock = Any() + @GuardedBy("lock") + private val cache = LruCache<Uri, RequestRecord>(cacheSize) + @GuardedBy("lock") + private val runningRequests = HashMap<Uri, RequestRecord>() - override suspend fun invoke(uri: Uri): Bitmap? = loadImageAsync(uri) + override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? = loadImageAsync(uri, caching) override fun loadImage(uri: Uri, callback: Consumer<Bitmap?>) { lifecycle.coroutineScope.launch { - val image = loadImageAsync(uri) + val image = loadImageAsync(uri, caching = true) if (isActive) { callback.accept(image) } @@ -65,23 +75,44 @@ class ImagePreviewImageLoader @JvmOverloads constructor( override fun prePopulate(uris: List<Uri>) { uris.asSequence().take(cache.maxSize()).forEach { uri -> lifecycle.coroutineScope.launch { - loadImageAsync(uri) + loadImageAsync(uri, caching = true) } } } - 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) + private suspend fun loadImageAsync(uri: Uri, caching: Boolean): Bitmap? { + return getRequestDeferred(uri, caching) + .await() + } + + private fun getRequestDeferred(uri: Uri, caching: Boolean): Deferred<Bitmap?> { + var shouldLaunchImageLoading = false + val request = synchronized(lock) { + cache[uri] + ?: runningRequests.getOrPut(uri) { + shouldLaunchImageLoading = true + RequestRecord(uri, CompletableDeferred(), caching) + }.apply { + this.caching = this.caching || caching } + } + if (shouldLaunchImageLoading) { + request.loadBitmapAsync() + } + return request.deferred + } + + private fun RequestRecord.loadBitmapAsync() { + lifecycle.coroutineScope.launch(dispatcher) { + loadBitmap() + }.invokeOnCompletion { cause -> + if (cause is CancellationException) { + cancel() } - }.await() + } } - private fun CompletableDeferred<Bitmap?>.loadBitmap(uri: Uri) { + private fun RequestRecord.loadBitmap() { val bitmap = try { context.contentResolver.loadThumbnail(uri, thumbnailSize, null) } catch (t: Throwable) { @@ -90,4 +121,27 @@ class ImagePreviewImageLoader @JvmOverloads constructor( } complete(bitmap) } + + private fun RequestRecord.cancel() { + synchronized(lock) { + runningRequests.remove(uri) + deferred.cancel() + } + } + + private fun RequestRecord.complete(bitmap: Bitmap?) { + deferred.complete(bitmap) + synchronized(lock) { + runningRequests.remove(uri) + if (bitmap != null && caching) { + cache.put(uri, this) + } + } + } + + private class RequestRecord( + val uri: Uri, + val deferred: CompletableDeferred<Bitmap?>, + @GuardedBy("lock") var caching: Boolean + ) } diff --git a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java index 56027a16..181fe117 100644 --- a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java @@ -38,7 +38,6 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; -import com.android.intentresolver.ImageLoader; import com.android.intentresolver.widget.ActionRow; import com.android.intentresolver.widget.ImagePreviewView.TransitionElementStatusCallback; diff --git a/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt b/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt new file mode 100644 index 00000000..225807ee --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/ImageLoader.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview + +import android.graphics.Bitmap +import android.net.Uri +import java.util.function.Consumer + +/** + * A content preview image loader. + */ +interface ImageLoader : suspend (Uri) -> Bitmap?, suspend (Uri, Boolean) -> Bitmap? { + /** + * Load preview image asynchronously; caching is allowed. + * @param uri content URI + * @param callback a callback that will be invoked with the loaded image or null if loading has + * failed. + */ + fun loadImage(uri: Uri, callback: Consumer<Bitmap?>) + + /** + * Prepopulate the image loader cache. + */ + fun prePopulate(uris: List<Uri>) + + /** + * Load preview image; caching is allowed. + */ + override suspend fun invoke(uri: Uri) = invoke(uri, true) + + /** + * Load preview image. + * @param uri content URI + * @param caching indicates if the loaded image could be cached. + */ + override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? +} diff --git a/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java index ece0c312..6bf9a1cc 100644 --- a/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java @@ -27,7 +27,6 @@ import android.widget.TextView; import androidx.annotation.Nullable; -import com.android.intentresolver.ImageLoader; import com.android.intentresolver.R; import com.android.intentresolver.widget.ActionRow; diff --git a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java index 9ce875c8..709ec566 100644 --- a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java @@ -31,7 +31,6 @@ import android.widget.TextView; import androidx.annotation.Nullable; -import com.android.intentresolver.ImageLoader; import com.android.intentresolver.R; import com.android.intentresolver.widget.ActionRow; import com.android.intentresolver.widget.ImagePreviewView.TransitionElementStatusCallback; diff --git a/java/src/com/android/intentresolver/widget/ImagePreviewView.kt b/java/src/com/android/intentresolver/widget/ImagePreviewView.kt index 5f92b149..3f0458ee 100644 --- a/java/src/com/android/intentresolver/widget/ImagePreviewView.kt +++ b/java/src/com/android/intentresolver/widget/ImagePreviewView.kt @@ -16,12 +16,8 @@ package com.android.intentresolver.widget -import android.graphics.Bitmap -import android.net.Uri import android.view.View -internal typealias ImageLoader = suspend (Uri) -> Bitmap? - interface ImagePreviewView { fun setTransitionElementStatusCallback(callback: TransitionElementStatusCallback?) fun getTransitionView(): View? diff --git a/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt b/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt index 7755610d..524b4f81 100644 --- a/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt +++ b/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt @@ -56,6 +56,8 @@ 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? + class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { constructor(context: Context) : this(context, null) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) @@ -131,7 +133,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { return null } - fun setPreviews(previews: List<Preview>, otherItemCount: Int, imageLoader: ImageLoader) { + fun setPreviews(previews: List<Preview>, otherItemCount: Int, imageLoader: CachingImageLoader) { previewAdapter.reset(0, imageLoader) batchLoader?.cancel() batchLoader = BatchPreviewLoader( @@ -176,8 +178,6 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { ) { constructor(type: PreviewType, uri: Uri) : this(type, uri, "1:1") - internal var bitmap: Bitmap? = null - internal fun updateAspectRatio(width: Int, height: Int) { if (width <= 0 || height <= 0) return val aspectRatio = width.toFloat() / height.toFloat() @@ -197,7 +197,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { private val context: Context ) : RecyclerView.Adapter<ViewHolder>() { private val previews = ArrayList<Preview>() - private var imageLoader: ImageLoader? = null + private var imageLoader: CachingImageLoader? = null private var firstImagePos = -1 private var totalItemCount: Int = 0 @@ -206,7 +206,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { var transitionStatusElementCallback: TransitionElementStatusCallback? = null - fun reset(totalItemCount: Int, imageLoader: ImageLoader) { + fun reset(totalItemCount: Int, imageLoader: CachingImageLoader) { this.imageLoader = imageLoader firstImagePos = -1 previews.clear() @@ -299,7 +299,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { fun bind( preview: Preview, - imageLoader: ImageLoader, + imageLoader: CachingImageLoader, isSharedTransitionElement: Boolean, previewReadyCallback: ((String) -> Unit)? ) { @@ -334,11 +334,11 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { } } - private suspend fun loadImage(preview: Preview, imageLoader: ImageLoader) { - val bitmap = preview.bitmap ?: runCatching { + private suspend fun loadImage(preview: Preview, imageLoader: CachingImageLoader) { + val bitmap = runCatching { // it's expected for all loading/caching optimizations to be implemented by the // loader - imageLoader(preview.uri) + imageLoader(preview.uri, true) }.getOrNull() image.setImageBitmap(bitmap) } @@ -384,7 +384,7 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { private class BatchPreviewLoader( private val adapter: Adapter, - private val imageLoader: ImageLoader, + private val imageLoader: CachingImageLoader, previews: List<Preview>, otherItemCount: Int, private val onNoPreviewCallback: (() -> Unit) @@ -435,18 +435,15 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { launch { while (pendingPreviews.isNotEmpty()) { val preview = pendingPreviews.poll() ?: continue + val isVisible = loadedPreviewWidth < maxWidth val bitmap = runCatching { // TODO: decide on adding a timeout - imageLoader(preview.uri) + imageLoader(preview.uri, isVisible) }.getOrNull() ?: continue preview.updateAspectRatio(bitmap.width, bitmap.height) updates.add(preview) - if (loadedPreviewWidth < maxWidth) { + if (isVisible) { loadedPreviewWidth += previewWidthCalculator(bitmap) - // cache bitmaps for the first preview items to aovid potential - // double-loading (in case those values are evicted from the image - // loader's cache) - preview.bitmap = bitmap if (loadedPreviewWidth >= maxWidth) { // notify that the preview now can be displayed reportFlow.emit(updateEvent) diff --git a/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java b/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java index 2a4d654a..9ebeb79d 100644 --- a/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java +++ b/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java @@ -28,6 +28,7 @@ import android.os.UserHandle; import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker; import com.android.intentresolver.chooser.TargetInfo; +import com.android.intentresolver.contentpreview.ImageLoader; import com.android.intentresolver.flags.FeatureFlagRepository; import com.android.intentresolver.shortcuts.ShortcutLoader; diff --git a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java index dc9baade..d23e4a66 100644 --- a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java +++ b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java @@ -35,6 +35,7 @@ import android.os.UserHandle; import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; +import com.android.intentresolver.contentpreview.ImageLoader; import com.android.intentresolver.flags.FeatureFlagRepository; import com.android.intentresolver.grid.ChooserGridAdapter; import com.android.intentresolver.shortcuts.ShortcutLoader; diff --git a/java/tests/src/com/android/intentresolver/ImagePreviewImageLoaderTest.kt b/java/tests/src/com/android/intentresolver/ImagePreviewImageLoaderTest.kt index f327e19e..3c399cc4 100644 --- a/java/tests/src/com/android/intentresolver/ImagePreviewImageLoaderTest.kt +++ b/java/tests/src/com/android/intentresolver/ImagePreviewImageLoaderTest.kt @@ -19,11 +19,18 @@ package com.android.intentresolver import android.content.ContentResolver import android.content.Context import android.content.res.Resources +import android.graphics.Bitmap import android.net.Uri import android.util.Size import androidx.lifecycle.Lifecycle +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineStart.UNDISPATCHED import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestCoroutineScheduler import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain @@ -41,7 +48,10 @@ class ImagePreviewImageLoaderTest { private val imageSize = Size(300, 300) private val uriOne = Uri.parse("content://org.package.app/image-1.png") private val uriTwo = Uri.parse("content://org.package.app/image-2.png") - private val contentResolver = mock<ContentResolver>() + private val bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + private val contentResolver = mock<ContentResolver> { + whenever(loadThumbnail(any(), any(), anyOrNull())).thenReturn(bitmap) + } private val resources = mock<Resources> { whenever(getDimensionPixelSize(R.dimen.chooser_preview_image_max_dimen)) .thenReturn(imageSize.width) @@ -70,7 +80,7 @@ class ImagePreviewImageLoaderTest { } @Test - fun test_prePopulate() = runTest { + fun prePopulate_cachesImagesUpToTheCacheSize() = runTest { testSubject.prePopulate(listOf(uriOne, uriTwo)) verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) @@ -81,7 +91,7 @@ class ImagePreviewImageLoaderTest { } @Test - fun test_invoke_return_cached_image() = runTest { + fun invoke_returnCachedImageWhenCalledTwice() = runTest { testSubject(uriOne) testSubject(uriOne) @@ -89,7 +99,33 @@ class ImagePreviewImageLoaderTest { } @Test - fun test_invoke_old_records_evicted_from_the_cache() = runTest { + fun invoke_whenInstructed_doesNotCache() = runTest { + testSubject(uriOne, false) + testSubject(uriOne, false) + + verify(contentResolver, times(2)).loadThumbnail(any(), any(), anyOrNull()) + } + + @Test + fun invoke_overlappedRequests_Deduplicate() = runTest { + val scheduler = TestCoroutineScheduler() + val dispatcher = StandardTestDispatcher(scheduler) + val testSubject = ImagePreviewImageLoader(context, lifecycleOwner.lifecycle, 1, dispatcher) + coroutineScope { + launch(start = UNDISPATCHED) { + testSubject(uriOne, false) + } + launch(start = UNDISPATCHED) { + testSubject(uriOne, false) + } + scheduler.advanceUntilIdle() + } + + verify(contentResolver, times(1)).loadThumbnail(any(), any(), anyOrNull()) + } + + @Test + fun invoke_oldRecordsEvictedFromTheCache() = runTest { testSubject(uriOne) testSubject(uriTwo) testSubject(uriTwo) @@ -98,4 +134,53 @@ class ImagePreviewImageLoaderTest { verify(contentResolver, times(2)).loadThumbnail(uriOne, imageSize, null) verify(contentResolver, times(1)).loadThumbnail(uriTwo, imageSize, null) } + + @Test + fun invoke_doNotCacheNulls() = runTest { + whenever(contentResolver.loadThumbnail(any(), any(), anyOrNull())).thenReturn(null) + testSubject(uriOne) + testSubject(uriOne) + + verify(contentResolver, times(2)).loadThumbnail(uriOne, imageSize, null) + } + + @Test(expected = CancellationException::class) + fun invoke_onClosedImageLoaderScope_throwsCancellationException() = runTest { + lifecycleOwner.state = Lifecycle.State.DESTROYED + testSubject(uriOne) + } + + @Test(expected = CancellationException::class) + fun invoke_imageLoaderScopeClosedMidflight_throwsCancellationException() = runTest { + val scheduler = TestCoroutineScheduler() + val dispatcher = StandardTestDispatcher(scheduler) + val testSubject = ImagePreviewImageLoader(context, lifecycleOwner.lifecycle, 1, dispatcher) + coroutineScope { + val deferred = async(start = UNDISPATCHED) { + testSubject(uriOne, false) + } + lifecycleOwner.state = Lifecycle.State.DESTROYED + scheduler.advanceUntilIdle() + deferred.await() + } + } + + @Test + fun invoke_multipleCallsWithDifferentCacheInstructions_cachingPrevails() = runTest { + val scheduler = TestCoroutineScheduler() + val dispatcher = StandardTestDispatcher(scheduler) + val testSubject = ImagePreviewImageLoader(context, lifecycleOwner.lifecycle, 1, dispatcher) + coroutineScope { + launch(start = UNDISPATCHED) { + testSubject(uriOne, false) + } + launch(start = UNDISPATCHED) { + testSubject(uriOne, true) + } + scheduler.advanceUntilIdle() + } + testSubject(uriOne, true) + + verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) + } } diff --git a/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt b/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt index 2f240d58..74a253b8 100644 --- a/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt +++ b/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt @@ -18,6 +18,7 @@ package com.android.intentresolver import android.graphics.Bitmap import android.net.Uri +import com.android.intentresolver.contentpreview.ImageLoader import java.util.function.Consumer internal class TestPreviewImageLoader( @@ -27,6 +28,7 @@ internal class TestPreviewImageLoader( callback.accept(bitmaps[uri]) } - override suspend fun invoke(uri: Uri): Bitmap? = bitmaps[uri] + override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? = bitmaps[uri] + override fun prePopulate(uris: List<Uri>) = Unit } diff --git a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java index 0a60b8c7..de5498db 100644 --- a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java +++ b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java @@ -104,6 +104,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import com.android.intentresolver.chooser.DisplayResolveInfo; +import com.android.intentresolver.contentpreview.ImageLoader; import com.android.intentresolver.shortcuts.ShortcutLoader; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; diff --git a/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt index 7b9a0ce6..8eec289e 100644 --- a/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt +++ b/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt @@ -21,7 +21,6 @@ import android.content.ContentInterface import android.content.Intent import android.graphics.Bitmap import android.net.Uri -import com.android.intentresolver.ImageLoader import com.android.intentresolver.any import com.android.intentresolver.anyOrNull import com.android.intentresolver.contentpreview.ChooserContentPreviewUi.ActionFactory @@ -48,7 +47,7 @@ class ChooserContentPreviewUiTest { callback.accept(null) } override fun prePopulate(uris: List<Uri>) = Unit - override suspend fun invoke(uri: Uri): Bitmap? = null + override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? = null } private val actionFactory = object : ActionFactory { override fun createCopyButton() = ActionRow.Action(label = "Copy", icon = null) {} |