From 32b53b791bd7a5bbacdf4266f78d2ae961638a88 Mon Sep 17 00:00:00 2001 From: Artem Kholodnyi Date: Fri, 23 Feb 2024 11:55:39 -0800 Subject: [PATCH] Convert ImagePipeline to Kotlin (initial) Reviewed By: oprisnik Differential Revision: D53944214 fbshipit-source-id: 1711ad8ca54fdba6bbc22be7fa1a9522bd7ad1c0 --- .../imagepipeline/core/ImagePipeline.java | 1174 ----------------- .../imagepipeline/core/ImagePipeline.kt | 1083 +++++++++++++++ .../BaseBitmapReferenceDataSubscriber.java | 3 +- .../listener/ForwardingRequestListener2.kt | 9 +- .../imagepipeline/core/ImagePipelineTest.java | 17 +- .../core/impl/FrescoVitoPrefetcherImpl.kt | 12 +- .../core/impl/NoOpFrescoVitoPrefetcher.kt | 10 +- .../fresco/vito/core/FrescoVitoPrefetcher.kt | 10 +- .../fresco/vito/litho/FrescoVitoImage2Spec.kt | 20 +- 9 files changed, 1130 insertions(+), 1208 deletions(-) delete mode 100644 imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipeline.java create mode 100644 imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipeline.kt diff --git a/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipeline.java b/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipeline.java deleted file mode 100644 index 85ba05c7ab..0000000000 --- a/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipeline.java +++ /dev/null @@ -1,1174 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.imagepipeline.core; - -import android.net.Uri; -import bolts.Continuation; -import bolts.Task; -import com.facebook.cache.common.CacheKey; -import com.facebook.callercontext.CallerContextVerifier; -import com.facebook.common.internal.Objects; -import com.facebook.common.internal.Preconditions; -import com.facebook.common.internal.Predicate; -import com.facebook.common.internal.Supplier; -import com.facebook.common.memory.PooledByteBuffer; -import com.facebook.common.references.CloseableReference; -import com.facebook.common.util.UriUtil; -import com.facebook.datasource.DataSource; -import com.facebook.datasource.DataSources; -import com.facebook.datasource.SimpleDataSource; -import com.facebook.imagepipeline.cache.BufferedDiskCache; -import com.facebook.imagepipeline.cache.CacheKeyFactory; -import com.facebook.imagepipeline.cache.MemoryCache; -import com.facebook.imagepipeline.common.Priority; -import com.facebook.imagepipeline.datasource.CloseableProducerToDataSourceAdapter; -import com.facebook.imagepipeline.datasource.ProducerToDataSourceAdapter; -import com.facebook.imagepipeline.image.CloseableImage; -import com.facebook.imagepipeline.listener.ForwardingRequestListener; -import com.facebook.imagepipeline.listener.ForwardingRequestListener2; -import com.facebook.imagepipeline.listener.RequestListener; -import com.facebook.imagepipeline.listener.RequestListener2; -import com.facebook.imagepipeline.producers.InternalRequestListener; -import com.facebook.imagepipeline.producers.Producer; -import com.facebook.imagepipeline.producers.SettableProducerContext; -import com.facebook.imagepipeline.producers.ThreadHandoffProducerQueue; -import com.facebook.imagepipeline.request.ImageRequest; -import com.facebook.imagepipeline.request.ImageRequestBuilder; -import com.facebook.imagepipeline.systrace.FrescoSystrace; -import com.facebook.infer.annotation.Nullsafe; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CancellationException; -import java.util.concurrent.atomic.AtomicLong; -import javax.annotation.Nullable; -import javax.annotation.concurrent.ThreadSafe; - -/** The entry point for the image pipeline. */ -@ThreadSafe -@Nullsafe(Nullsafe.Mode.LOCAL) -public class ImagePipeline { - - private static final CancellationException PREFETCH_EXCEPTION = - new CancellationException("Prefetching is not enabled"); - - private final ProducerSequenceFactory mProducerSequenceFactory; - private final RequestListener mRequestListener; - private final RequestListener2 mRequestListener2; - private final Supplier mIsPrefetchEnabledSupplier; - private final MemoryCache mBitmapMemoryCache; - private final MemoryCache mEncodedMemoryCache; - private final BufferedDiskCache mMainBufferedDiskCache; - private final BufferedDiskCache mSmallImageBufferedDiskCache; - private final CacheKeyFactory mCacheKeyFactory; - private final ThreadHandoffProducerQueue mThreadHandoffProducerQueue; - private final Supplier mSuppressBitmapPrefetchingSupplier; - private AtomicLong mIdCounter; - private final Supplier mLazyDataSource; - private final @Nullable CallerContextVerifier mCallerContextVerifier; - private final ImagePipelineConfigInterface mConfig; - - public ImagePipeline( - ProducerSequenceFactory producerSequenceFactory, - Set requestListeners, - Set requestListener2s, - Supplier isPrefetchEnabledSupplier, - MemoryCache bitmapMemoryCache, - MemoryCache encodedMemoryCache, - BufferedDiskCache mainBufferedDiskCache, - BufferedDiskCache smallImageBufferedDiskCache, - CacheKeyFactory cacheKeyFactory, - ThreadHandoffProducerQueue threadHandoffProducerQueue, - Supplier suppressBitmapPrefetchingSupplier, - Supplier lazyDataSource, - @Nullable CallerContextVerifier callerContextVerifier, - ImagePipelineConfigInterface config) { - mIdCounter = new AtomicLong(); - mProducerSequenceFactory = producerSequenceFactory; - mRequestListener = new ForwardingRequestListener(requestListeners); - mRequestListener2 = new ForwardingRequestListener2(requestListener2s); - mIsPrefetchEnabledSupplier = isPrefetchEnabledSupplier; - mBitmapMemoryCache = bitmapMemoryCache; - mEncodedMemoryCache = encodedMemoryCache; - mMainBufferedDiskCache = mainBufferedDiskCache; - mSmallImageBufferedDiskCache = smallImageBufferedDiskCache; - mCacheKeyFactory = cacheKeyFactory; - mThreadHandoffProducerQueue = threadHandoffProducerQueue; - mSuppressBitmapPrefetchingSupplier = suppressBitmapPrefetchingSupplier; - mLazyDataSource = lazyDataSource; - mCallerContextVerifier = callerContextVerifier; - mConfig = config; - } - - /** - * Generates unique id for RequestFuture. - * - * @return unique id - */ - public String generateUniqueFutureId() { - return String.valueOf(mIdCounter.getAndIncrement()); - } - - /** - * Returns a DataSource supplier that will on get submit the request for execution and return a - * DataSource representing the pending results of the task. - * - * @param imageRequest the request to submit (what to execute). - * @param callerContext the caller context of the caller of data source supplier - * @param requestLevel which level to look down until for the image - * @return a DataSource representing pending results and completion of the request - */ - public Supplier>> getDataSourceSupplier( - final ImageRequest imageRequest, - final @Nullable Object callerContext, - final ImageRequest.RequestLevel requestLevel) { - return new Supplier>>() { - @Override - public DataSource> get() { - return fetchDecodedImage(imageRequest, callerContext, requestLevel); - } - - @Override - public String toString() { - return Objects.toStringHelper(this).add("uri", imageRequest.getSourceUri()).toString(); - } - }; - } - - /** - * Returns a DataSource supplier that will on get submit the request for execution and return a - * DataSource representing the pending results of the task. - * - * @param imageRequest the request to submit (what to execute). - * @param callerContext the caller context of the caller of data source supplier - * @param requestLevel which level to look down until for the image - * @param requestListener additional image request listener independent of ImageRequest listeners - * @return a DataSource representing pending results and completion of the request - */ - public Supplier>> getDataSourceSupplier( - final ImageRequest imageRequest, - final @Nullable Object callerContext, - final ImageRequest.RequestLevel requestLevel, - final @Nullable RequestListener requestListener) { - return new Supplier>>() { - @Override - public DataSource> get() { - return fetchDecodedImage(imageRequest, callerContext, requestLevel, requestListener); - } - - @Override - public String toString() { - return Objects.toStringHelper(this).add("uri", imageRequest.getSourceUri()).toString(); - } - }; - } - - /** - * Returns a DataSource supplier that will on get submit the request for execution and return a - * DataSource representing the pending results of the task. - * - * @param imageRequest the request to submit (what to execute). - * @param callerContext the caller context of the caller of data source supplier - * @param requestLevel which level to look down until for the image - * @param requestListener additional image request listener independent of ImageRequest listeners - * @param uiComponentId optional UI component ID requesting the image - * @return a DataSource representing pending results and completion of the request - */ - public Supplier>> getDataSourceSupplier( - final ImageRequest imageRequest, - final @Nullable Object callerContext, - final ImageRequest.RequestLevel requestLevel, - final @Nullable RequestListener requestListener, - final @Nullable String uiComponentId) { - return new Supplier>>() { - @Override - public DataSource> get() { - return fetchDecodedImage( - imageRequest, callerContext, requestLevel, requestListener, uiComponentId); - } - - @Override - public String toString() { - return Objects.toStringHelper(this).add("uri", imageRequest.getSourceUri()).toString(); - } - }; - } - - /** - * Returns a DataSource supplier that will on get submit the request for execution and return a - * DataSource representing the pending results of the task. - * - * @param imageRequest the request to submit (what to execute). - * @return a DataSource representing pending results and completion of the request - */ - public Supplier>> - getEncodedImageDataSourceSupplier( - final ImageRequest imageRequest, final @Nullable Object callerContext) { - return new Supplier>>() { - @Override - public DataSource> get() { - return fetchEncodedImage(imageRequest, callerContext); - } - - @Override - public String toString() { - return Objects.toStringHelper(this).add("uri", imageRequest.getSourceUri()).toString(); - } - }; - } - - /** - * Submits a request for bitmap cache lookup. - * - * @param imageRequest the request to submit - * @param callerContext the caller context for image request - * @return a DataSource representing the image - */ - public DataSource> fetchImageFromBitmapCache( - ImageRequest imageRequest, @Nullable Object callerContext) { - return fetchDecodedImage( - imageRequest, callerContext, ImageRequest.RequestLevel.BITMAP_MEMORY_CACHE); - } - - /** - * Submits a request for execution and returns a DataSource representing the pending decoded - * image(s). - * - *

The returned DataSource must be closed once the client has finished with it. - * - * @param imageRequest the request to submit - * @param callerContext the caller context for image request - * @return a DataSource representing the pending decoded image(s) - */ - public DataSource> fetchDecodedImage( - @Nullable ImageRequest imageRequest, @Nullable Object callerContext) { - return fetchDecodedImage(imageRequest, callerContext, ImageRequest.RequestLevel.FULL_FETCH); - } - - /** - * Submits a request for execution and returns a DataSource representing the pending decoded - * image(s). - * - *

The returned DataSource must be closed once the client has finished with it. - * - * @param imageRequest the request to submit - * @param callerContext the caller context for image request - * @param requestListener additional image request listener independent of ImageRequest listeners - * @return a DataSource representing the pending decoded image(s) - */ - public DataSource> fetchDecodedImage( - ImageRequest imageRequest, - @Nullable Object callerContext, - @Nullable RequestListener requestListener) { - return fetchDecodedImage( - imageRequest, callerContext, ImageRequest.RequestLevel.FULL_FETCH, requestListener); - } - - /** - * Submits a request for execution and returns a DataSource representing the pending decoded - * image(s). - * - *

The returned DataSource must be closed once the client has finished with it. - * - * @param imageRequest the request to submit - * @param callerContext the caller context for image request - * @param lowestPermittedRequestLevelOnSubmit the lowest request level permitted for image request - * @return a DataSource representing the pending decoded image(s) - */ - public DataSource> fetchDecodedImage( - @Nullable ImageRequest imageRequest, - @Nullable Object callerContext, - ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit) { - return fetchDecodedImage( - imageRequest, callerContext, lowestPermittedRequestLevelOnSubmit, null); - } - - /** - * Submits a request for execution and returns a DataSource representing the pending decoded - * image(s). - * - *

The returned DataSource must be closed once the client has finished with it. - * - * @param imageRequest the request to submit - * @param callerContext the caller context for image request - * @param lowestPermittedRequestLevelOnSubmit the lowest request level permitted for image reques - * @param requestListener additional image request listener independent of ImageRequest listeners - * @return a DataSource representing the pending decoded image(s) - */ - public DataSource> fetchDecodedImage( - @Nullable ImageRequest imageRequest, - @Nullable Object callerContext, - ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit, - @Nullable RequestListener requestListener) { - return fetchDecodedImage( - imageRequest, callerContext, lowestPermittedRequestLevelOnSubmit, requestListener, null); - } - - /** - * Submits a request for execution and returns a DataSource representing the pending decoded - * image(s). - * - *

The returned DataSource must be closed once the client has finished with it. - * - * @param imageRequest the request to submit - * @param callerContext the caller context for image request - * @param lowestPermittedRequestLevelOnSubmit the lowest request level permitted for image reques - * @param requestListener additional image request listener independent of ImageRequest listeners - * @param uiComponentId optional UI component ID that is requesting the image - * @return a DataSource representing the pending decoded image(s) - */ - public DataSource> fetchDecodedImage( - @Nullable ImageRequest imageRequest, - @Nullable Object callerContext, - ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit, - @Nullable RequestListener requestListener, - @Nullable String uiComponentId) { - try { - Preconditions.checkNotNull(imageRequest); - Producer> producerSequence = - mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest); - return submitFetchRequest( - producerSequence, - imageRequest, - lowestPermittedRequestLevelOnSubmit, - callerContext, - requestListener, - uiComponentId); - } catch (Exception exception) { - return DataSources.immediateFailedDataSource(exception); - } - } - - /** - * Submits a request for execution and returns a DataSource representing the pending decoded - * image(s). - * - *

The returned DataSource must be closed once the client has finished with it. - * - * @param imageRequest the request to submit - * @param callerContext the caller context for image request - * @param lowestPermittedRequestLevelOnSubmit the lowest request level permitted for image reques - * @param requestListener additional image request listener independent of ImageRequest listeners - * @param uiComponentId optional UI component ID that is requesting the image - * @param extras optional extra data - * @return a DataSource representing the pending decoded image(s) - */ - public DataSource> fetchDecodedImage( - ImageRequest imageRequest, - @Nullable Object callerContext, - ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit, - @Nullable RequestListener requestListener, - @Nullable String uiComponentId, - @Nullable Map extras) { - try { - Producer> producerSequence = - mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest); - return submitFetchRequest( - producerSequence, - imageRequest, - lowestPermittedRequestLevelOnSubmit, - callerContext, - requestListener, - uiComponentId, - extras); - } catch (Exception exception) { - return DataSources.immediateFailedDataSource(exception); - } - } - - /** - * Submits a request for execution and returns a DataSource representing the pending encoded - * image(s). - * - *

The ResizeOptions in the imageRequest will be ignored for this fetch - * - *

The returned DataSource must be closed once the client has finished with it. - * - * @param imageRequest the request to submit - * @return a DataSource representing the pending encoded image(s) - */ - public DataSource> fetchEncodedImage( - ImageRequest imageRequest, @Nullable Object callerContext) { - return fetchEncodedImage(imageRequest, callerContext, null); - } - - /** - * Submits a request for execution and returns a DataSource representing the pending encoded - * image(s). - * - *

The ResizeOptions in the imageRequest will be ignored for this fetch - * - *

The returned DataSource must be closed once the client has finished with it. - * - * @param imageRequest the request to submit - * @return a DataSource representing the pending encoded image(s) - */ - public DataSource> fetchEncodedImage( - ImageRequest imageRequest, - @Nullable Object callerContext, - @Nullable RequestListener requestListener) { - Preconditions.checkNotNull(imageRequest.getSourceUri()); - try { - Producer> producerSequence = - mProducerSequenceFactory.getEncodedImageProducerSequence(imageRequest); - // The resize options are used to determine whether images are going to be downsampled during - // decode or not. For the case where the image has to be downsampled and it's a local image it - // will be kept as a FileInputStream until decoding instead of reading it in memory. Since - // this method returns an encoded image, it should always be read into memory. Therefore, the - // resize options are ignored to avoid treating the image as if it was to be downsampled - // during decode. - if (imageRequest.getResizeOptions() != null) { - imageRequest = ImageRequestBuilder.fromRequest(imageRequest).setResizeOptions(null).build(); - } - return submitFetchRequest( - producerSequence, - imageRequest, - ImageRequest.RequestLevel.FULL_FETCH, - callerContext, - requestListener, - null, - null); - } catch (Exception exception) { - return DataSources.immediateFailedDataSource(exception); - } - } - - /** - * Submits a request for prefetching to the bitmap cache. - * - *

Beware that if your network fetcher doesn't support priorities prefetch requests may slow - * down images which are immediately required on screen. - * - * @param imageRequest the request to submit - * @return a DataSource that can safely be ignored. - */ - public DataSource prefetchToBitmapCache( - @Nullable ImageRequest imageRequest, @Nullable Object callerContext) { - return prefetchToBitmapCache(imageRequest, callerContext, null); - } - - public DataSource prefetchToBitmapCache( - @Nullable ImageRequest imageRequest, - @Nullable Object callerContext, - @Nullable RequestListener requestListener) { - try { - if (FrescoSystrace.isTracing()) { - FrescoSystrace.beginSection("ImagePipeline#prefetchToBitmapCache"); - } - - if (!mIsPrefetchEnabledSupplier.get()) { - return DataSources.immediateFailedDataSource(PREFETCH_EXCEPTION); - } - try { - if (mConfig.getExperiments() != null - && mConfig.getExperiments().getPrefetchShortcutEnabled() - && isInBitmapMemoryCache(imageRequest)) { - return DataSources.immediateSuccessfulDataSource(); - } - Preconditions.checkNotNull(imageRequest); - final Boolean shouldDecodePrefetches = imageRequest.shouldDecodePrefetches(); - final boolean skipBitmapCache = - shouldDecodePrefetches != null - ? !shouldDecodePrefetches // use imagerequest param if specified - : mSuppressBitmapPrefetchingSupplier - .get(); // otherwise fall back to pipeline's default - Producer producerSequence = - skipBitmapCache - ? mProducerSequenceFactory.getEncodedImagePrefetchProducerSequence(imageRequest) - : mProducerSequenceFactory.getDecodedImagePrefetchProducerSequence(imageRequest); - return submitPrefetchRequest( - producerSequence, - imageRequest, - ImageRequest.RequestLevel.FULL_FETCH, - callerContext, - Priority.MEDIUM, - requestListener); - } catch (Exception exception) { - return DataSources.immediateFailedDataSource(exception); - } - } finally { - if (FrescoSystrace.isTracing()) { - FrescoSystrace.endSection(); - } - } - } - - /** - * Submits a request for prefetching to the disk cache with a default priority. - * - *

Beware that if your network fetcher doesn't support priorities prefetch requests may slow - * down images which are immediately required on screen. - * - * @param imageRequest the request to submit - * @return a DataSource that can safely be ignored. - */ - public DataSource prefetchToDiskCache( - @Nullable ImageRequest imageRequest, @Nullable Object callerContext) { - return prefetchToDiskCache(imageRequest, callerContext, Priority.MEDIUM); - } - - public DataSource prefetchToDiskCache( - @Nullable ImageRequest imageRequest, - @Nullable Object callerContext, - @Nullable RequestListener requestListener) { - return prefetchToDiskCache(imageRequest, callerContext, Priority.MEDIUM, requestListener); - } - - /** - * Submits a request for prefetching to the disk cache. - * - *

Beware that if your network fetcher doesn't support priorities prefetch requests may slow - * down images which are immediately required on screen. - * - * @param imageRequest the request to submit - * @param priority custom priority for the fetch - * @return a DataSource that can safely be ignored. - */ - public DataSource prefetchToDiskCache( - @Nullable ImageRequest imageRequest, @Nullable Object callerContext, Priority priority) { - return prefetchToDiskCache(imageRequest, callerContext, priority, null); - } - - public DataSource prefetchToDiskCache( - @Nullable ImageRequest imageRequest, - @Nullable Object callerContext, - Priority priority, - @Nullable RequestListener requestListener) { - if (!mIsPrefetchEnabledSupplier.get()) { - return DataSources.immediateFailedDataSource(PREFETCH_EXCEPTION); - } - if (imageRequest == null) { - return DataSources.immediateFailedDataSource( - new NullPointerException("imageRequest is null")); - } - try { - Producer producerSequence = - mProducerSequenceFactory.getEncodedImagePrefetchProducerSequence(imageRequest); - return submitPrefetchRequest( - producerSequence, - imageRequest, - ImageRequest.RequestLevel.FULL_FETCH, - callerContext, - priority, - requestListener); - } catch (Exception exception) { - return DataSources.immediateFailedDataSource(exception); - } - } - - /** - * Submits a request for prefetching to the encoded cache with a default priority. - * - *

Beware that if your network fetcher doesn't support priorities prefetch requests may slow - * down images which are immediately required on screen. - * - * @param imageRequest the request to submit - * @return a DataSource that can safely be ignored. - */ - public DataSource prefetchToEncodedCache( - ImageRequest imageRequest, @Nullable Object callerContext) { - return prefetchToEncodedCache(imageRequest, callerContext, Priority.MEDIUM); - } - - public DataSource prefetchToEncodedCache( - ImageRequest imageRequest, - @Nullable Object callerContext, - @Nullable RequestListener requestListener) { - return prefetchToEncodedCache(imageRequest, callerContext, Priority.MEDIUM, requestListener); - } - - /** - * Submits a request for prefetching to the encoded cache. - * - *

Beware that if your network fetcher doesn't support priorities prefetch requests may slow - * down images which are immediately required on screen. - * - * @param imageRequest the request to submit - * @param priority custom priority for the fetch - * @return a DataSource that can safely be ignored. - */ - public DataSource prefetchToEncodedCache( - ImageRequest imageRequest, @Nullable Object callerContext, Priority priority) { - return prefetchToEncodedCache(imageRequest, callerContext, priority, null); - } - - public DataSource prefetchToEncodedCache( - ImageRequest imageRequest, - @Nullable Object callerContext, - Priority priority, - @Nullable RequestListener requestListener) { - try { - if (FrescoSystrace.isTracing()) { - FrescoSystrace.beginSection("ImagePipeline#prefetchToEncodedCache"); - } - - if (!mIsPrefetchEnabledSupplier.get()) { - return DataSources.immediateFailedDataSource(PREFETCH_EXCEPTION); - } - try { - if (mConfig.getExperiments() != null - && mConfig.getExperiments().getPrefetchShortcutEnabled() - && isInEncodedMemoryCache(imageRequest)) { - return DataSources.immediateSuccessfulDataSource(); - } - Producer producerSequence = - mProducerSequenceFactory.getEncodedImagePrefetchProducerSequence(imageRequest); - return submitPrefetchRequest( - producerSequence, - imageRequest, - ImageRequest.RequestLevel.FULL_FETCH, - callerContext, - priority, - requestListener); - } catch (Exception exception) { - return DataSources.immediateFailedDataSource(exception); - } - } finally { - if (FrescoSystrace.isTracing()) { - FrescoSystrace.endSection(); - } - } - } - - /** - * Removes all images with the specified {@link Uri} from memory cache. - * - * @param uri The uri of the image to evict - */ - public void evictFromMemoryCache(final Uri uri) { - Predicate predicate = predicateForUri(uri); - mBitmapMemoryCache.removeAll(predicate); - mEncodedMemoryCache.removeAll(predicate); - } - - /** - * If you have supplied your own cache key factory when configuring the pipeline, this method may - * not work correctly. It will only work if the custom factory builds the cache key entirely from - * the URI. If that is not the case, use {@link #evictFromDiskCache(ImageRequest)}. - * - * @param uri The uri of the image to evict - */ - public void evictFromDiskCache(final Uri uri) { - evictFromDiskCache(Preconditions.checkNotNull(ImageRequest.fromUri(uri))); - } - - /** - * Removes all images with the specified {@link Uri} from disk cache. - * - * @param imageRequest The imageRequest for the image to evict from disk cache - */ - public void evictFromDiskCache(final @Nullable ImageRequest imageRequest) { - if (imageRequest == null) { - return; - } - CacheKey cacheKey = mCacheKeyFactory.getEncodedCacheKey(imageRequest, null); - mMainBufferedDiskCache.remove(cacheKey); - mSmallImageBufferedDiskCache.remove(cacheKey); - } - - /** - * If you have supplied your own cache key factory when configuring the pipeline, this method may - * not work correctly. It will only work if the custom factory builds the cache key entirely from - * the URI. If that is not the case, use {@link #evictFromMemoryCache(Uri)} and {@link - * #evictFromDiskCache(ImageRequest)} separately. - * - * @param uri The uri of the image to evict - */ - public void evictFromCache(final Uri uri) { - evictFromMemoryCache(uri); - evictFromDiskCache(uri); - } - - /** Clear the memory caches */ - public void clearMemoryCaches() { - Predicate allPredicate = - new Predicate() { - @Override - public boolean apply(CacheKey key) { - return true; - } - }; - mBitmapMemoryCache.removeAll(allPredicate); - mEncodedMemoryCache.removeAll(allPredicate); - } - - /** Clear disk caches */ - public void clearDiskCaches() { - mMainBufferedDiskCache.clearAll(); - mSmallImageBufferedDiskCache.clearAll(); - } - - /** - * Current disk caches size - * - * @return size in Bytes - */ - public long getUsedDiskCacheSize() { - return mMainBufferedDiskCache.getSize() + mSmallImageBufferedDiskCache.getSize(); - } - - /** Clear all the caches (memory and disk) */ - public void clearCaches() { - clearMemoryCaches(); - clearDiskCaches(); - } - - /** - * Returns whether the image is stored in the bitmap memory cache. - * - * @param uri the uri for the image to be looked up. - * @return true if the image was found in the bitmap memory cache, false otherwise - */ - public boolean isInBitmapMemoryCache(@Nullable final Uri uri) { - if (uri == null) { - return false; - } - Predicate bitmapCachePredicate = predicateForUri(uri); - return mBitmapMemoryCache.contains(bitmapCachePredicate); - } - - /** @return The Bitmap MemoryCache */ - public MemoryCache getBitmapMemoryCache() { - return mBitmapMemoryCache; - } - - /** - * Returns whether the image is stored in the bitmap memory cache. - * - * @param imageRequest the imageRequest for the image to be looked up. - * @return true if the image was found in the bitmap memory cache, false otherwise. - */ - public boolean isInBitmapMemoryCache(@Nullable final ImageRequest imageRequest) { - if (imageRequest == null) { - return false; - } - final CacheKey cacheKey = mCacheKeyFactory.getBitmapCacheKey(imageRequest, null); - CloseableReference ref = mBitmapMemoryCache.get(cacheKey); - try { - return CloseableReference.isValid(ref); - } finally { - CloseableReference.closeSafely(ref); - } - } - - /** - * Returns whether the image is stored in the encoded memory cache. - * - * @param uri the uri for the image to be looked up. - * @return true if the image was found in the encoded memory cache, false otherwise - */ - public boolean isInEncodedMemoryCache(@Nullable final Uri uri) { - if (uri == null) { - return false; - } - Predicate encodedCachePredicate = predicateForUri(uri); - return mEncodedMemoryCache.contains(encodedCachePredicate); - } - - /** - * Returns whether the image is stored in the encoded memory cache. - * - * @param imageRequest the imageRequest for the image to be looked up. - * @return true if the image was found in the encoded memory cache, false otherwise. - */ - public boolean isInEncodedMemoryCache(@Nullable final ImageRequest imageRequest) { - if (imageRequest == null) { - return false; - } - final CacheKey cacheKey = mCacheKeyFactory.getEncodedCacheKey(imageRequest, null); - CloseableReference ref = mEncodedMemoryCache.get(cacheKey); - try { - return CloseableReference.isValid(ref); - } finally { - CloseableReference.closeSafely(ref); - } - } - - /** - * Returns whether the image is stored in the disk cache. Performs disk cache check synchronously. - * It is not recommended to use this unless you know what exactly you are doing. Disk cache check - * is a costly operation, the call will block the caller thread until the cache check is - * completed. - * - * @param uri the uri for the image to be looked up. - * @return true if the image was found in the disk cache, false otherwise. - */ - public boolean isInDiskCacheSync(final Uri uri) { - return isInDiskCacheSync(uri, ImageRequest.CacheChoice.SMALL) - || isInDiskCacheSync(uri, ImageRequest.CacheChoice.DEFAULT); - } - - /** - * Returns whether the image is stored in the disk cache. Performs disk cache check synchronously. - * It is not recommended to use this unless you know what exactly you are doing. Disk cache check - * is a costly operation, the call will block the caller thread until the cache check is - * completed. - * - * @param uri the uri for the image to be looked up. - * @param cacheChoice the cacheChoice for the cache to be looked up. - * @return true if the image was found in the disk cache, false otherwise. - */ - public boolean isInDiskCacheSync(final Uri uri, final ImageRequest.CacheChoice cacheChoice) { - ImageRequest imageRequest = - ImageRequestBuilder.newBuilderWithSource(uri).setCacheChoice(cacheChoice).build(); - return isInDiskCacheSync(imageRequest); - } - - /** - * Performs disk cache check synchronously. It is not recommended to use this unless you know what - * exactly you are doing. Disk cache check is a costly operation, the call will block the caller - * thread until the cache check is completed. - * - * @param imageRequest the imageRequest for the image to be looked up. - * @return true if the image was found in the disk cache, false otherwise. - */ - public boolean isInDiskCacheSync(final ImageRequest imageRequest) { - final CacheKey cacheKey = mCacheKeyFactory.getEncodedCacheKey(imageRequest, null); - final ImageRequest.CacheChoice cacheChoice = imageRequest.getCacheChoice(); - - switch (cacheChoice) { - case DEFAULT: - return mMainBufferedDiskCache.diskCheckSync(cacheKey); - case SMALL: - return mSmallImageBufferedDiskCache.diskCheckSync(cacheKey); - default: - return false; - } - } - - /** - * Returns whether the image is stored in the disk cache. - * - *

If you have supplied your own cache key factory when configuring the pipeline, this method - * may not work correctly. It will only work if the custom factory builds the cache key entirely - * from the URI. If that is not the case, use {@link #isInDiskCache(ImageRequest)}. - * - * @param uri the uri for the image to be looked up. - * @return true if the image was found in the disk cache, false otherwise. - */ - public DataSource isInDiskCache(final Uri uri) { - return isInDiskCache(Preconditions.checkNotNull(ImageRequest.fromUri(uri))); - } - - /** - * Returns whether the image is stored in the disk cache. - * - * @param imageRequest the imageRequest for the image to be looked up. - * @return true if the image was found in the disk cache, false otherwise. - */ - public DataSource isInDiskCache(final ImageRequest imageRequest) { - final CacheKey cacheKey = mCacheKeyFactory.getEncodedCacheKey(imageRequest, null); - final SimpleDataSource dataSource = SimpleDataSource.create(); - mMainBufferedDiskCache - .contains(cacheKey) - .continueWithTask( - new Continuation>() { - @Override - public Task then(Task task) throws Exception { - if (!task.isCancelled() && !task.isFaulted() && task.getResult()) { - return Task.forResult(true); - } - return mSmallImageBufferedDiskCache.contains(cacheKey); - } - }) - .continueWith( - new Continuation() { - @Nullable - @Override - public Void then(Task task) throws Exception { - dataSource.setResult(!task.isCancelled() && !task.isFaulted() && task.getResult()); - return null; - } - }); - return dataSource; - } - /** @return {@link CacheKey} for doing bitmap cache lookups in the pipeline. */ - @Nullable - public CacheKey getCacheKey(@Nullable ImageRequest imageRequest, @Nullable Object callerContext) { - if (FrescoSystrace.isTracing()) { - FrescoSystrace.beginSection("ImagePipeline#getCacheKey"); - } - final CacheKeyFactory cacheKeyFactory = mCacheKeyFactory; - CacheKey cacheKey = null; - if (cacheKeyFactory != null && imageRequest != null) { - if (imageRequest.getPostprocessor() != null) { - cacheKey = cacheKeyFactory.getPostprocessedBitmapCacheKey(imageRequest, callerContext); - } else { - cacheKey = cacheKeyFactory.getBitmapCacheKey(imageRequest, callerContext); - } - } - if (FrescoSystrace.isTracing()) { - FrescoSystrace.endSection(); - } - return cacheKey; - } - - /** - * Returns a reference to the cached image - * - * @param cacheKey - * @return a closeable reference or null if image was not present in cache - */ - @Nullable - public CloseableReference getCachedImage(@Nullable CacheKey cacheKey) { - MemoryCache memoryCache = mBitmapMemoryCache; - if (memoryCache == null || cacheKey == null) { - return null; - } - CloseableReference closeableImage = memoryCache.get(cacheKey); - if (closeableImage != null && !closeableImage.get().getQualityInfo().isOfFullQuality()) { - closeableImage.close(); - return null; - } - return closeableImage; - } - - public boolean hasCachedImage(@Nullable CacheKey cacheKey) { - MemoryCache memoryCache = mBitmapMemoryCache; - if (memoryCache == null || cacheKey == null) { - return false; - } - return memoryCache.contains(cacheKey); - } - - private DataSource> submitFetchRequest( - Producer> producerSequence, - ImageRequest imageRequest, - ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit, - @Nullable Object callerContext, - @Nullable RequestListener requestListener, - @Nullable String uiComponentId) { - return submitFetchRequest( - producerSequence, - imageRequest, - lowestPermittedRequestLevelOnSubmit, - callerContext, - requestListener, - uiComponentId, - null); - } - - private DataSource> submitFetchRequest( - Producer> producerSequence, - ImageRequest imageRequest, - ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit, - @Nullable Object callerContext, - @Nullable RequestListener requestListener, - @Nullable String uiComponentId, - @Nullable Map extras) { - if (FrescoSystrace.isTracing()) { - FrescoSystrace.beginSection("ImagePipeline#submitFetchRequest"); - } - final RequestListener2 requestListener2 = - new InternalRequestListener( - getRequestListenerForRequest(imageRequest, requestListener), mRequestListener2); - - if (mCallerContextVerifier != null) { - mCallerContextVerifier.verifyCallerContext(callerContext, false); - } - - try { - ImageRequest.RequestLevel lowestPermittedRequestLevel = - ImageRequest.RequestLevel.getMax( - imageRequest.getLowestPermittedRequestLevel(), lowestPermittedRequestLevelOnSubmit); - SettableProducerContext settableProducerContext = - new SettableProducerContext( - imageRequest, - generateUniqueFutureId(), - uiComponentId, - requestListener2, - callerContext, - lowestPermittedRequestLevel, - /* isPrefetch */ false, - imageRequest.getProgressiveRenderingEnabled() - || !UriUtil.isNetworkUri(imageRequest.getSourceUri()), - imageRequest.getPriority(), - mConfig); - settableProducerContext.putExtras(extras); - return CloseableProducerToDataSourceAdapter.create( - producerSequence, settableProducerContext, requestListener2); - } catch (Exception exception) { - return DataSources.immediateFailedDataSource(exception); - } finally { - if (FrescoSystrace.isTracing()) { - FrescoSystrace.endSection(); - } - } - } - - private DataSource> submitFetchRequest( - Producer> producerSequence, - ImageRequest imageRequest, - ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit, - @Nullable Object callerContext, - @Nullable RequestListener requestListener, - @Nullable Map extras) { - if (FrescoSystrace.isTracing()) { - FrescoSystrace.beginSection("ImagePipeline#submitFetchRequest"); - } - final RequestListener2 requestListener2 = - new InternalRequestListener( - getRequestListenerForRequest(imageRequest, requestListener), mRequestListener2); - - if (mCallerContextVerifier != null) { - mCallerContextVerifier.verifyCallerContext(callerContext, false); - } - - try { - ImageRequest.RequestLevel lowestPermittedRequestLevel = - ImageRequest.RequestLevel.getMax( - imageRequest.getLowestPermittedRequestLevel(), lowestPermittedRequestLevelOnSubmit); - SettableProducerContext settableProducerContext = - new SettableProducerContext( - imageRequest, - generateUniqueFutureId(), - null, - requestListener2, - callerContext, - lowestPermittedRequestLevel, - /* isPrefetch */ false, - imageRequest.getProgressiveRenderingEnabled() - || !UriUtil.isNetworkUri(imageRequest.getSourceUri()), - imageRequest.getPriority(), - mConfig); - return CloseableProducerToDataSourceAdapter.create( - producerSequence, settableProducerContext, requestListener2); - } catch (Exception exception) { - return DataSources.immediateFailedDataSource(exception); - } finally { - if (FrescoSystrace.isTracing()) { - FrescoSystrace.endSection(); - } - } - } - - public DataSource> submitFetchRequest( - Producer> producerSequence, - SettableProducerContext settableProducerContext, - RequestListener requestListener) { - if (FrescoSystrace.isTracing()) { - FrescoSystrace.beginSection("ImagePipeline#submitFetchRequest"); - } - try { - final RequestListener2 requestListener2 = - new InternalRequestListener(requestListener, mRequestListener2); - - return CloseableProducerToDataSourceAdapter.create( - producerSequence, settableProducerContext, requestListener2); - } catch (Exception exception) { - return DataSources.immediateFailedDataSource(exception); - } finally { - if (FrescoSystrace.isTracing()) { - FrescoSystrace.endSection(); - } - } - } - - public ProducerSequenceFactory getProducerSequenceFactory() { - return mProducerSequenceFactory; - } - - private DataSource submitPrefetchRequest( - Producer producerSequence, - ImageRequest imageRequest, - ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit, - @Nullable Object callerContext, - Priority priority, - @Nullable RequestListener requestListener) { - final RequestListener2 requestListener2 = - new InternalRequestListener( - getRequestListenerForRequest(imageRequest, requestListener), mRequestListener2); - - if (mCallerContextVerifier != null) { - mCallerContextVerifier.verifyCallerContext(callerContext, true); - } - try { - ImageRequest.RequestLevel lowestPermittedRequestLevel = - ImageRequest.RequestLevel.getMax( - imageRequest.getLowestPermittedRequestLevel(), lowestPermittedRequestLevelOnSubmit); - SettableProducerContext settableProducerContext = - new SettableProducerContext( - imageRequest, - generateUniqueFutureId(), - requestListener2, - callerContext, - lowestPermittedRequestLevel, - /* isPrefetch */ true, - mConfig.getExperiments() != null - && mConfig.getExperiments().getAllowProgressiveOnPrefetch() - && imageRequest.getProgressiveRenderingEnabled(), - priority, - mConfig); - return ProducerToDataSourceAdapter.create( - producerSequence, settableProducerContext, requestListener2); - } catch (Exception exception) { - return DataSources.immediateFailedDataSource(exception); - } - } - - public RequestListener getRequestListenerForRequest( - ImageRequest imageRequest, @Nullable RequestListener requestListener) { - if (requestListener == null) { - if (imageRequest.getRequestListener() == null) { - return mRequestListener; - } - return new ForwardingRequestListener(mRequestListener, imageRequest.getRequestListener()); - } else { - if (imageRequest.getRequestListener() == null) { - return new ForwardingRequestListener(mRequestListener, requestListener); - } - return new ForwardingRequestListener( - mRequestListener, requestListener, imageRequest.getRequestListener()); - } - } - - public RequestListener getCombinedRequestListener(@Nullable RequestListener listener) { - if (listener == null) { - return mRequestListener; - } - return new ForwardingRequestListener(mRequestListener, listener); - } - - private Predicate predicateForUri(final Uri uri) { - return new Predicate() { - @Override - public boolean apply(CacheKey key) { - return key.containsUri(uri); - } - }; - } - - public void pause() { - mThreadHandoffProducerQueue.startQueueing(); - } - - public void resume() { - mThreadHandoffProducerQueue.stopQueuing(); - } - - public boolean isPaused() { - return mThreadHandoffProducerQueue.isQueueing(); - } - - public Supplier isLazyDataSource() { - return mLazyDataSource; - } - - /** @return The CacheKeyFactory implementation used by ImagePipeline */ - public CacheKeyFactory getCacheKeyFactory() { - return mCacheKeyFactory; - } - - public ImagePipelineConfigInterface getConfig() { - return mConfig; - } - - public void init() { - // Yes, this does nothing. It's a placeholder method to be used in locations where - // an injection would otherwise appear to be unused. - } -} diff --git a/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipeline.kt b/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipeline.kt new file mode 100644 index 0000000000..0904b84830 --- /dev/null +++ b/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipeline.kt @@ -0,0 +1,1083 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.imagepipeline.core + +import android.net.Uri +import bolts.Task +import com.facebook.cache.common.CacheKey +import com.facebook.callercontext.CallerContextVerifier +import com.facebook.common.internal.Objects +import com.facebook.common.internal.Predicate +import com.facebook.common.internal.Supplier +import com.facebook.common.memory.PooledByteBuffer +import com.facebook.common.references.CloseableReference +import com.facebook.common.util.UriUtil +import com.facebook.datasource.DataSource +import com.facebook.datasource.DataSources +import com.facebook.datasource.SimpleDataSource +import com.facebook.imagepipeline.cache.BufferedDiskCache +import com.facebook.imagepipeline.cache.CacheKeyFactory +import com.facebook.imagepipeline.cache.MemoryCache +import com.facebook.imagepipeline.common.Priority +import com.facebook.imagepipeline.datasource.CloseableProducerToDataSourceAdapter +import com.facebook.imagepipeline.datasource.ProducerToDataSourceAdapter.Companion.create +import com.facebook.imagepipeline.image.CloseableImage +import com.facebook.imagepipeline.listener.ForwardingRequestListener +import com.facebook.imagepipeline.listener.ForwardingRequestListener2 +import com.facebook.imagepipeline.listener.RequestListener +import com.facebook.imagepipeline.listener.RequestListener2 +import com.facebook.imagepipeline.producers.InternalRequestListener +import com.facebook.imagepipeline.producers.Producer +import com.facebook.imagepipeline.producers.SettableProducerContext +import com.facebook.imagepipeline.producers.ThreadHandoffProducerQueue +import com.facebook.imagepipeline.request.ImageRequest +import com.facebook.imagepipeline.request.ImageRequest.CacheChoice +import com.facebook.imagepipeline.request.ImageRequest.RequestLevel +import com.facebook.imagepipeline.request.ImageRequestBuilder +import com.facebook.imagepipeline.systrace.FrescoSystrace +import java.util.concurrent.CancellationException +import java.util.concurrent.atomic.AtomicLong +import javax.annotation.concurrent.ThreadSafe + +/** The entry point for the image pipeline. */ +@ThreadSafe +class ImagePipeline( + val producerSequenceFactory: ProducerSequenceFactory, + requestListeners: Set, + requestListener2s: Set?, + private val isPrefetchEnabledSupplier: Supplier, + bitmapMemoryCache: MemoryCache, + encodedMemoryCache: MemoryCache, + mainBufferedDiskCache: BufferedDiskCache, + smallImageBufferedDiskCache: BufferedDiskCache, + cacheKeyFactory: CacheKeyFactory, + threadHandoffProducerQueue: ThreadHandoffProducerQueue, + suppressBitmapPrefetchingSupplier: Supplier, + lazyDataSource: Supplier, + callerContextVerifier: CallerContextVerifier?, + config: ImagePipelineConfigInterface +) { + + private val requestListener: RequestListener = ForwardingRequestListener(requestListeners) + private val requestListener2: RequestListener2 = ForwardingRequestListener2(requestListener2s) + + /** @return The Bitmap MemoryCache */ + val bitmapMemoryCache: MemoryCache + private val encodedMemoryCache: MemoryCache + private val mainBufferedDiskCache: BufferedDiskCache + private val smallImageBufferedDiskCache: BufferedDiskCache + + /** @return The CacheKeyFactory implementation used by ImagePipeline */ + val cacheKeyFactory: CacheKeyFactory + private val threadHandoffProducerQueue: ThreadHandoffProducerQueue + private val suppressBitmapPrefetchingSupplier: Supplier + private val idCounter: AtomicLong = AtomicLong() + val isLazyDataSource: Supplier + private val callerContextVerifier: CallerContextVerifier? + val config: ImagePipelineConfigInterface + + /** + * Generates unique id for RequestFuture. + * + * @return unique id + */ + fun generateUniqueFutureId(): String = idCounter.getAndIncrement().toString() + + /** + * Returns a DataSource supplier that will on get submit the request for execution and return a + * DataSource representing the pending results of the task. + * + * @param imageRequest the request to submit (what to execute). + * @param callerContext the caller context of the caller of data source supplier + * @param requestLevel which level to look down until for the image + * @return a DataSource representing pending results and completion of the request + */ + fun getDataSourceSupplier( + imageRequest: ImageRequest, + callerContext: Any?, + requestLevel: RequestLevel? + ): Supplier>> { + return object : Supplier>> { + override fun get() = fetchDecodedImage(imageRequest, callerContext, requestLevel) + + override fun toString(): String = + Objects.toStringHelper(this).add("uri", imageRequest.sourceUri).toString() + } + } + + /** + * Returns a DataSource supplier that will on get submit the request for execution and return a + * DataSource representing the pending results of the task. + * + * @param imageRequest the request to submit (what to execute). + * @param callerContext the caller context of the caller of data source supplier + * @param requestLevel which level to look down until for the image + * @param requestListener additional image request listener independent of ImageRequest listeners + * @return a DataSource representing pending results and completion of the request + */ + fun getDataSourceSupplier( + imageRequest: ImageRequest, + callerContext: Any?, + requestLevel: RequestLevel?, + requestListener: RequestListener? + ): Supplier>> { + return object : Supplier>> { + override fun get() = + fetchDecodedImage(imageRequest, callerContext, requestLevel, requestListener) + + override fun toString(): String = + Objects.toStringHelper(this).add("uri", imageRequest.sourceUri).toString() + } + } + + /** + * Returns a DataSource supplier that will on get submit the request for execution and return a + * DataSource representing the pending results of the task. + * + * @param imageRequest the request to submit (what to execute). + * @param callerContext the caller context of the caller of data source supplier + * @param requestLevel which level to look down until for the image + * @param requestListener additional image request listener independent of ImageRequest listeners + * @param uiComponentId optional UI component ID requesting the image + * @return a DataSource representing pending results and completion of the request + */ + fun getDataSourceSupplier( + imageRequest: ImageRequest, + callerContext: Any?, + requestLevel: RequestLevel?, + requestListener: RequestListener?, + uiComponentId: String? + ): Supplier>> { + return object : Supplier>> { + override fun get() = + fetchDecodedImage( + imageRequest, callerContext, requestLevel, requestListener, uiComponentId) + + override fun toString(): String = + Objects.toStringHelper(this).add("uri", imageRequest.sourceUri).toString() + } + } + + /** + * Returns a DataSource supplier that will on get submit the request for execution and return a + * DataSource representing the pending results of the task. + * + * @param imageRequest the request to submit (what to execute). + * @return a DataSource representing pending results and completion of the request + */ + fun getEncodedImageDataSourceSupplier( + imageRequest: ImageRequest, + callerContext: Any? + ): Supplier>> { + return object : Supplier>> { + override fun get() = fetchEncodedImage(imageRequest, callerContext) + + override fun toString(): String = + Objects.toStringHelper(this).add("uri", imageRequest.sourceUri).toString() + } + } + + /** + * Submits a request for bitmap cache lookup. + * + * @param imageRequest the request to submit + * @param callerContext the caller context for image request + * @return a DataSource representing the image + */ + fun fetchImageFromBitmapCache( + imageRequest: ImageRequest, + callerContext: Any? + ): DataSource> = + fetchDecodedImage(imageRequest, callerContext, RequestLevel.BITMAP_MEMORY_CACHE) + + /** + * Submits a request for execution and returns a DataSource representing the pending decoded + * image(s). + * + * The returned DataSource must be closed once the client has finished with it. + * + * @param imageRequest the request to submit + * @param callerContext the caller context for image request + * @param lowestPermittedRequestLevelOnSubmit the lowest request level permitted for image reques + * @param requestListener additional image request listener independent of ImageRequest listeners + * @param uiComponentId optional UI component ID that is requesting the image + * @return a DataSource representing the pending decoded image(s) + */ + /** + * Submits a request for execution and returns a DataSource representing the pending decoded + * image(s). + * + * The returned DataSource must be closed once the client has finished with it. + * + * @param imageRequest the request to submit + * @param callerContext the caller context for image request + * @param lowestPermittedRequestLevelOnSubmit the lowest request level permitted for image reques + * @param requestListener additional image request listener independent of ImageRequest listeners + * @return a DataSource representing the pending decoded image(s) + */ + /** + * Submits a request for execution and returns a DataSource representing the pending decoded + * image(s). + * + * The returned DataSource must be closed once the client has finished with it. + * + * @param imageRequest the request to submit + * @param callerContext the caller context for image request + * @param lowestPermittedRequestLevelOnSubmit the lowest request level permitted for image request + * @return a DataSource representing the pending decoded image(s) + */ + /** + * Submits a request for execution and returns a DataSource representing the pending decoded + * image(s). + * + * The returned DataSource must be closed once the client has finished with it. + * + * @param imageRequest the request to submit + * @param callerContext the caller context for image request + * @return a DataSource representing the pending decoded image(s) + */ + fun fetchDecodedImage( + imageRequest: ImageRequest?, + callerContext: Any?, + lowestPermittedRequestLevelOnSubmit: RequestLevel? = null, + requestListener: RequestListener? = null, + uiComponentId: String? = null + ): DataSource> { + if (imageRequest == null) { + return DataSources.immediateFailedDataSource(NullPointerException()) + } + return try { + imageRequest + val producerSequence = producerSequenceFactory.getDecodedImageProducerSequence(imageRequest) + submitFetchRequest( + producerSequence, + imageRequest, + lowestPermittedRequestLevelOnSubmit ?: RequestLevel.FULL_FETCH, + callerContext, + requestListener, + uiComponentId) + } catch (exception: Exception) { + DataSources.immediateFailedDataSource(exception) + } + } + + fun fetchDecodedImage( + imageRequest: ImageRequest?, + callerContext: Any? + ): DataSource> { + return fetchDecodedImage(imageRequest, callerContext, null) + } + + fun fetchDecodedImage( + imageRequest: ImageRequest?, + callerContext: Any?, + listener: RequestListener + ): DataSource> { + return fetchDecodedImage( + imageRequest, + callerContext, + lowestPermittedRequestLevelOnSubmit = null, + requestListener = listener) + } + + fun fetchDecodedImage( + imageRequest: ImageRequest?, + callerContext: Any?, + lowestPermittedRequestLevelOnSubmit: RequestLevel, + ): DataSource> { + return fetchDecodedImage( + imageRequest, + callerContext, + lowestPermittedRequestLevelOnSubmit = lowestPermittedRequestLevelOnSubmit, + requestListener = null) + } + + /** + * Submits a request for execution and returns a DataSource representing the pending decoded + * image(s). + * + * The returned DataSource must be closed once the client has finished with it. + * + * @param imageRequest the request to submit + * @param callerContext the caller context for image request + * @param lowestPermittedRequestLevelOnSubmit the lowest request level permitted for image request + * @param requestListener additional image request listener independent of ImageRequest listeners + * @param uiComponentId optional UI component ID that is requesting the image + * @param extras optional extra data + * @return a DataSource representing the pending decoded image(s) + */ + fun fetchDecodedImage( + imageRequest: ImageRequest?, + callerContext: Any?, + lowestPermittedRequestLevelOnSubmit: RequestLevel, + requestListener: RequestListener?, + uiComponentId: String?, + extras: Map? + ): DataSource> { + if (imageRequest == null) { + return DataSources.immediateFailedDataSource(NullPointerException()) + } + return try { + val producerSequence = producerSequenceFactory.getDecodedImageProducerSequence(imageRequest) + submitFetchRequest( + producerSequence, + imageRequest, + lowestPermittedRequestLevelOnSubmit, + callerContext, + requestListener, + uiComponentId, + extras) + } catch (exception: Exception) { + DataSources.immediateFailedDataSource(exception) + } + } + + /** + * Submits a request for execution and returns a DataSource representing the pending encoded + * image(s). + * + * The ResizeOptions in the imageRequest will be ignored for this fetch + * + * The returned DataSource must be closed once the client has finished with it. + * + * @param imageRequest the request to submit + * @return a DataSource representing the pending encoded image(s) + */ + fun fetchEncodedImage( + imageRequest: ImageRequest, + callerContext: Any? + ): DataSource> = + fetchEncodedImage(imageRequest, callerContext, null) + + /** + * Submits a request for execution and returns a DataSource representing the pending encoded + * image(s). + * + * The ResizeOptions in the imageRequest will be ignored for this fetch + * + * The returned DataSource must be closed once the client has finished with it. + * + * @param imageRequest the request to submit + * @return a DataSource representing the pending encoded image(s) + */ + fun fetchEncodedImage( + imageRequest: ImageRequest, + callerContext: Any?, + requestListener: RequestListener? + ): DataSource> { + var imageRequest = imageRequest + checkNotNull(imageRequest.sourceUri) + return try { + val producerSequence = producerSequenceFactory.getEncodedImageProducerSequence(imageRequest) + // The resize options are used to determine whether images are going to be downsampled during + // decode or not. For the case where the image has to be downsampled and it's a local image it + // will be kept as a FileInputStream until decoding instead of reading it in memory. Since + // this method returns an encoded image, it should always be read into memory. Therefore, the + // resize options are ignored to avoid treating the image as if it was to be downsampled + // during decode. + if (imageRequest.resizeOptions != null) { + imageRequest = ImageRequestBuilder.fromRequest(imageRequest).setResizeOptions(null).build() + } + submitFetchRequest( + producerSequence, + imageRequest, + RequestLevel.FULL_FETCH, + callerContext, + requestListener, + null, + null) + } catch (exception: Exception) { + DataSources.immediateFailedDataSource(exception) + } + } + + @JvmOverloads + fun prefetchToBitmapCache( + imageRequest: ImageRequest?, + callerContext: Any?, + ): DataSource = prefetchToBitmapCache(imageRequest, callerContext, null) + + /** + * Submits a request for prefetching to the bitmap cache. + * + * Beware that if your network fetcher doesn't support priorities prefetch requests may slow down + * images which are immediately required on screen. + * + * @param imageRequest the request to submit + * @return a DataSource that can safely be ignored. + */ + @JvmOverloads + fun prefetchToBitmapCache( + imageRequest: ImageRequest?, + callerContext: Any?, + requestListener: RequestListener? + ): DataSource { + return try { + if (FrescoSystrace.isTracing()) { + FrescoSystrace.beginSection("ImagePipeline#prefetchToBitmapCache") + } + if (!isPrefetchEnabledSupplier.get()) { + return DataSources.immediateFailedDataSource(PREFETCH_EXCEPTION) + } + try { + if (config.experiments.prefetchShortcutEnabled && isInBitmapMemoryCache(imageRequest)) { + return DataSources.immediateSuccessfulDataSource() + } + checkNotNull(imageRequest) + val shouldDecodePrefetches = imageRequest.shouldDecodePrefetches() + val skipBitmapCache = + if (shouldDecodePrefetches != null) + !shouldDecodePrefetches // use imagerequest param if specified + else + suppressBitmapPrefetchingSupplier.get() // otherwise fall back to pipeline's default + val producerSequence = + if (skipBitmapCache) + producerSequenceFactory.getEncodedImagePrefetchProducerSequence(imageRequest) + else producerSequenceFactory.getDecodedImagePrefetchProducerSequence(imageRequest) + submitPrefetchRequest( + producerSequence, + imageRequest, + RequestLevel.FULL_FETCH, + callerContext, + Priority.MEDIUM, + requestListener) + } catch (exception: Exception) { + DataSources.immediateFailedDataSource(exception) + } + } finally { + if (FrescoSystrace.isTracing()) { + FrescoSystrace.endSection() + } + } + } + + fun prefetchToDiskCache( + imageRequest: ImageRequest?, + callerContext: Any?, + requestListener: RequestListener? + ): DataSource = + prefetchToDiskCache(imageRequest, callerContext, Priority.MEDIUM, requestListener) + + fun prefetchToDiskCache(imageRequest: ImageRequest?, callerContext: Any?): DataSource { + return prefetchToDiskCache(imageRequest, callerContext, Priority.MEDIUM, null) + } + + fun prefetchToDiskCache( + imageRequest: ImageRequest?, + callerContext: Any?, + priority: Priority + ): DataSource { + return prefetchToDiskCache(imageRequest, callerContext, priority, null) + } + /** + * Submits a request for prefetching to the disk cache. + * + * Beware that if your network fetcher doesn't support priorities prefetch requests may slow down + * images which are immediately required on screen. + * + * @param imageRequest the request to submit + * @param priority custom priority for the fetch + * @return a DataSource that can safely be ignored. + */ + /** + * Submits a request for prefetching to the disk cache with a default priority. + * + * Beware that if your network fetcher doesn't support priorities prefetch requests may slow down + * images which are immediately required on screen. + * + * @param imageRequest the request to submit + * @return a DataSource that can safely be ignored. + */ + @JvmOverloads + fun prefetchToDiskCache( + imageRequest: ImageRequest?, + callerContext: Any?, + priority: Priority, + requestListener: RequestListener? + ): DataSource { + if (!isPrefetchEnabledSupplier.get()) { + return DataSources.immediateFailedDataSource(PREFETCH_EXCEPTION) + } + return if (imageRequest == null) { + DataSources.immediateFailedDataSource(NullPointerException("imageRequest is null")) + } else { + try { + val producerSequence = + producerSequenceFactory.getEncodedImagePrefetchProducerSequence(imageRequest) + submitPrefetchRequest( + producerSequence, + imageRequest, + RequestLevel.FULL_FETCH, + callerContext, + priority, + requestListener) + } catch (exception: Exception) { + DataSources.immediateFailedDataSource(exception) + } + } + } + + fun prefetchToEncodedCache( + imageRequest: ImageRequest?, + callerContext: Any?, + requestListener: RequestListener? + ): DataSource = + prefetchToEncodedCache(imageRequest, callerContext, Priority.MEDIUM, requestListener) + + /** + * Submits a request for prefetching to the encoded cache. + * + * Beware that if your network fetcher doesn't support priorities prefetch requests may slow down + * images which are immediately required on screen. + * + * @param imageRequest the request to submit + * @param priority custom priority for the fetch + * @return a DataSource that can safely be ignored. + */ + /** + * Submits a request for prefetching to the encoded cache with a default priority. + * + * Beware that if your network fetcher doesn't support priorities prefetch requests may slow down + * images which are immediately required on screen. + * + * @param imageRequest the request to submit + * @return a DataSource that can safely be ignored. + */ + @JvmOverloads + fun prefetchToEncodedCache( + imageRequest: ImageRequest?, + callerContext: Any?, + priority: Priority = Priority.MEDIUM, + requestListener: RequestListener? = null + ): DataSource { + return try { + if (FrescoSystrace.isTracing()) { + FrescoSystrace.beginSection("ImagePipeline#prefetchToEncodedCache") + } + if (!isPrefetchEnabledSupplier.get()) { + return DataSources.immediateFailedDataSource(PREFETCH_EXCEPTION) + } + try { + if (config.experiments?.prefetchShortcutEnabled == true && + isInEncodedMemoryCache(imageRequest)) { + return DataSources.immediateSuccessfulDataSource() + } + val producerSequence = + producerSequenceFactory.getEncodedImagePrefetchProducerSequence(imageRequest!!) + submitPrefetchRequest( + producerSequence, + imageRequest, + RequestLevel.FULL_FETCH, + callerContext, + priority, + requestListener) + } catch (exception: Exception) { + DataSources.immediateFailedDataSource(exception) + } + } finally { + if (FrescoSystrace.isTracing()) { + FrescoSystrace.endSection() + } + } + } + + /** + * Removes all images with the specified [Uri] from memory cache. + * + * @param uri The uri of the image to evict + */ + fun evictFromMemoryCache(uri: Uri) { + val predicate = predicateForUri(uri) + bitmapMemoryCache.removeAll(predicate) + encodedMemoryCache.removeAll(predicate) + } + + /** + * If you have supplied your own cache key factory when configuring the pipeline, this method may + * not work correctly. It will only work if the custom factory builds the cache key entirely from + * the URI. If that is not the case, use [evictFromDiskCache(ImageRequest)]. + * + * @param uri The uri of the image to evict + */ + fun evictFromDiskCache(uri: Uri?) { + evictFromDiskCache(checkNotNull(ImageRequest.fromUri(uri))) + } + + /** + * Removes all images with the specified [Uri] from disk cache. + * + * @param imageRequest The imageRequest for the image to evict from disk cache + */ + fun evictFromDiskCache(imageRequest: ImageRequest?) { + if (imageRequest == null) { + return + } + val cacheKey = cacheKeyFactory.getEncodedCacheKey(imageRequest, null) + mainBufferedDiskCache.remove(cacheKey) + smallImageBufferedDiskCache.remove(cacheKey) + } + + /** + * If you have supplied your own cache key factory when configuring the pipeline, this method may + * not work correctly. It will only work if the custom factory builds the cache key entirely from + * the URI. If that is not the case, use [evictFromMemoryCache(Uri)] and + * [evictFromDiskCache(ImageRequest)] separately. + * + * @param uri The uri of the image to evict + */ + fun evictFromCache(uri: Uri) { + evictFromMemoryCache(uri) + evictFromDiskCache(uri) + } + + /** Clear the memory caches */ + fun clearMemoryCaches() { + val allPredicate: Predicate = Predicate { true } + bitmapMemoryCache.removeAll(allPredicate) + encodedMemoryCache.removeAll(allPredicate) + } + + /** Clear disk caches */ + fun clearDiskCaches() { + mainBufferedDiskCache.clearAll() + smallImageBufferedDiskCache.clearAll() + } + + val usedDiskCacheSize: Long + /** + * Current disk caches size + * + * @return size in Bytes + */ + get() = mainBufferedDiskCache.size + smallImageBufferedDiskCache.size + + /** Clear all the caches (memory and disk) */ + fun clearCaches() { + clearMemoryCaches() + clearDiskCaches() + } + + /** + * Returns whether the image is stored in the bitmap memory cache. + * + * @param uri the uri for the image to be looked up. + * @return true if the image was found in the bitmap memory cache, false otherwise + */ + fun isInBitmapMemoryCache(uri: Uri?): Boolean { + if (uri == null) { + return false + } + val bitmapCachePredicate = predicateForUri(uri) + return bitmapMemoryCache.contains(bitmapCachePredicate) + } + + /** + * Returns whether the image is stored in the bitmap memory cache. + * + * @param imageRequest the imageRequest for the image to be looked up. + * @return true if the image was found in the bitmap memory cache, false otherwise. + */ + fun isInBitmapMemoryCache(imageRequest: ImageRequest?): Boolean { + if (imageRequest == null) { + return false + } + val cacheKey = cacheKeyFactory.getBitmapCacheKey(imageRequest, null) + val ref = bitmapMemoryCache[cacheKey] + return try { + CloseableReference.isValid(ref) + } finally { + CloseableReference.closeSafely(ref) + } + } + + /** + * Returns whether the image is stored in the encoded memory cache. + * + * @param uri the uri for the image to be looked up. + * @return true if the image was found in the encoded memory cache, false otherwise + */ + fun isInEncodedMemoryCache(uri: Uri?): Boolean { + if (uri == null) { + return false + } + val encodedCachePredicate = predicateForUri(uri) + return encodedMemoryCache.contains(encodedCachePredicate) + } + + /** + * Returns whether the image is stored in the encoded memory cache. + * + * @param imageRequest the imageRequest for the image to be looked up. + * @return true if the image was found in the encoded memory cache, false otherwise. + */ + fun isInEncodedMemoryCache(imageRequest: ImageRequest?): Boolean { + if (imageRequest == null) { + return false + } + val cacheKey = cacheKeyFactory.getEncodedCacheKey(imageRequest, null) + val ref = encodedMemoryCache[cacheKey] + return try { + CloseableReference.isValid(ref) + } finally { + CloseableReference.closeSafely(ref) + } + } + + /** + * Returns whether the image is stored in the disk cache. Performs disk cache check synchronously. + * It is not recommended to use this unless you know what exactly you are doing. Disk cache check + * is a costly operation, the call will block the caller thread until the cache check is + * completed. + * + * @param uri the uri for the image to be looked up. + * @return true if the image was found in the disk cache, false otherwise. + */ + fun isInDiskCacheSync(uri: Uri?): Boolean = + isInDiskCacheSync(uri, CacheChoice.SMALL) || isInDiskCacheSync(uri, CacheChoice.DEFAULT) + + /** + * Returns whether the image is stored in the disk cache. Performs disk cache check synchronously. + * It is not recommended to use this unless you know what exactly you are doing. Disk cache check + * is a costly operation, the call will block the caller thread until the cache check is + * completed. + * + * @param uri the uri for the image to be looked up. + * @param cacheChoice the cacheChoice for the cache to be looked up. + * @return true if the image was found in the disk cache, false otherwise. + */ + fun isInDiskCacheSync(uri: Uri?, cacheChoice: CacheChoice?): Boolean { + val imageRequest = + ImageRequestBuilder.newBuilderWithSource(uri).setCacheChoice(cacheChoice).build() + return isInDiskCacheSync(imageRequest) + } + + /** + * Performs disk cache check synchronously. It is not recommended to use this unless you know what + * exactly you are doing. Disk cache check is a costly operation, the call will block the caller + * thread until the cache check is completed. + * + * @param imageRequest the imageRequest for the image to be looked up. + * @return true if the image was found in the disk cache, false otherwise. + */ + fun isInDiskCacheSync(imageRequest: ImageRequest): Boolean { + val cacheKey = cacheKeyFactory.getEncodedCacheKey(imageRequest, null) + val cacheChoice = imageRequest.cacheChoice + return when (cacheChoice) { + CacheChoice.DEFAULT -> mainBufferedDiskCache.diskCheckSync(cacheKey) + CacheChoice.SMALL -> smallImageBufferedDiskCache.diskCheckSync(cacheKey) + else -> false + } + } + + /** + * Returns whether the image is stored in the disk cache. + * + * If you have supplied your own cache key factory when configuring the pipeline, this method may + * not work correctly. It will only work if the custom factory builds the cache key entirely from + * the URI. If that is not the case, use [isInDiskCache(ImageRequest)]. + * + * @param uri the uri for the image to be looked up. + * @return true if the image was found in the disk cache, false otherwise. + */ + fun isInDiskCache(uri: Uri?): DataSource = + isInDiskCache(checkNotNull(ImageRequest.fromUri(uri))) + + /** + * Returns whether the image is stored in the disk cache. + * + * @param imageRequest the imageRequest for the image to be looked up. + * @return true if the image was found in the disk cache, false otherwise. + */ + fun isInDiskCache(imageRequest: ImageRequest?): DataSource { + val cacheKey = cacheKeyFactory.getEncodedCacheKey(imageRequest, null) + val dataSource = SimpleDataSource.create() + mainBufferedDiskCache + .contains(cacheKey) + .continueWithTask { task -> + if (!task.isCancelled && !task.isFaulted && task.result) { + Task.forResult(true) + } else { + smallImageBufferedDiskCache.contains(cacheKey) + } + } + .continueWith { task -> + dataSource.result = !task.isCancelled && !task.isFaulted && task.result + null + } + return dataSource + } + + /** @return [CacheKey] for doing bitmap cache lookups in the pipeline. */ + fun getCacheKey(imageRequest: ImageRequest?, callerContext: Any?): CacheKey? { + if (FrescoSystrace.isTracing()) { + FrescoSystrace.beginSection("ImagePipeline#getCacheKey") + } + val cacheKeyFactory = cacheKeyFactory + var cacheKey: CacheKey? = null + if (cacheKeyFactory != null && imageRequest != null) { + cacheKey = + if (imageRequest.postprocessor != null) { + cacheKeyFactory.getPostprocessedBitmapCacheKey(imageRequest, callerContext) + } else { + cacheKeyFactory.getBitmapCacheKey(imageRequest, callerContext) + } + } + if (FrescoSystrace.isTracing()) { + FrescoSystrace.endSection() + } + return cacheKey + } + + /** + * Returns a reference to the cached image + * + * @param cacheKey + * @return a closeable reference or null if image was not present in cache + */ + fun getCachedImage(cacheKey: CacheKey?): CloseableReference? { + val memoryCache = bitmapMemoryCache + if (memoryCache == null || cacheKey == null) { + return null + } + val closeableImage = memoryCache[cacheKey] + if (closeableImage != null && !closeableImage.get().qualityInfo.isOfFullQuality) { + closeableImage.close() + return null + } + return closeableImage + } + + fun hasCachedImage(cacheKey: CacheKey?): Boolean { + val memoryCache = bitmapMemoryCache + return if (memoryCache == null || cacheKey == null) { + false + } else { + memoryCache.contains(cacheKey) + } + } + + private fun submitFetchRequest( + producerSequence: Producer>, + imageRequest: ImageRequest, + lowestPermittedRequestLevelOnSubmit: RequestLevel, + callerContext: Any?, + requestListener: RequestListener?, + uiComponentId: String? + ): DataSource> = + submitFetchRequest( + producerSequence, + imageRequest, + lowestPermittedRequestLevelOnSubmit, + callerContext, + requestListener, + uiComponentId, + null) + + private fun submitFetchRequest( + producerSequence: Producer>, + imageRequest: ImageRequest, + lowestPermittedRequestLevelOnSubmit: RequestLevel, + callerContext: Any?, + requestListener: RequestListener?, + uiComponentId: String?, + extras: Map? + ): DataSource> { + if (FrescoSystrace.isTracing()) { + FrescoSystrace.beginSection("ImagePipeline#submitFetchRequest") + } + val requestListener2 = + InternalRequestListener( + getRequestListenerForRequest(imageRequest, requestListener), requestListener2) + callerContextVerifier?.verifyCallerContext(callerContext, false) + return try { + val lowestPermittedRequestLevel = + RequestLevel.getMax( + imageRequest.lowestPermittedRequestLevel, lowestPermittedRequestLevelOnSubmit) + val settableProducerContext = + SettableProducerContext( + imageRequest, + generateUniqueFutureId(), + uiComponentId, + requestListener2, + callerContext, + lowestPermittedRequestLevel, /* isPrefetch */ + false, + imageRequest.progressiveRenderingEnabled || + !UriUtil.isNetworkUri(imageRequest.sourceUri), + imageRequest.priority, + config) + settableProducerContext.putExtras(extras) + CloseableProducerToDataSourceAdapter.create( + producerSequence, settableProducerContext, requestListener2) + } catch (exception: Exception) { + DataSources.immediateFailedDataSource(exception) + } finally { + if (FrescoSystrace.isTracing()) { + FrescoSystrace.endSection() + } + } + } + + private fun submitFetchRequest( + producerSequence: Producer>, + imageRequest: ImageRequest, + lowestPermittedRequestLevelOnSubmit: RequestLevel, + callerContext: Any?, + requestListener: RequestListener?, + extras: Map? + ): DataSource> { + if (FrescoSystrace.isTracing()) { + FrescoSystrace.beginSection("ImagePipeline#submitFetchRequest") + } + val requestListener2 = + InternalRequestListener( + getRequestListenerForRequest(imageRequest, requestListener), requestListener2) + callerContextVerifier?.verifyCallerContext(callerContext, false) + return try { + val lowestPermittedRequestLevel = + RequestLevel.getMax( + imageRequest.lowestPermittedRequestLevel, lowestPermittedRequestLevelOnSubmit) + val settableProducerContext = + SettableProducerContext( + imageRequest, + generateUniqueFutureId(), + null, + requestListener2, + callerContext, + lowestPermittedRequestLevel, /* isPrefetch */ + false, + imageRequest.progressiveRenderingEnabled || + !UriUtil.isNetworkUri(imageRequest.sourceUri), + imageRequest.priority, + config) + CloseableProducerToDataSourceAdapter.create( + producerSequence, settableProducerContext, requestListener2) + } catch (exception: Exception) { + DataSources.immediateFailedDataSource(exception) + } finally { + if (FrescoSystrace.isTracing()) { + FrescoSystrace.endSection() + } + } + } + + fun submitFetchRequest( + producerSequence: Producer?>, + settableProducerContext: SettableProducerContext, + requestListener: RequestListener? + ): DataSource> { + if (FrescoSystrace.isTracing()) { + FrescoSystrace.beginSection("ImagePipeline#submitFetchRequest") + } + return try { + val requestListener2 = InternalRequestListener(requestListener, requestListener2) + CloseableProducerToDataSourceAdapter.create( + producerSequence, settableProducerContext, requestListener2) + } catch (exception: Exception) { + DataSources.immediateFailedDataSource(exception) + } finally { + if (FrescoSystrace.isTracing()) { + FrescoSystrace.endSection() + } + } + } + + private fun submitPrefetchRequest( + producerSequence: Producer, + imageRequest: ImageRequest, + lowestPermittedRequestLevelOnSubmit: RequestLevel, + callerContext: Any?, + priority: Priority, + requestListener: RequestListener? + ): DataSource { + val requestListener2 = + InternalRequestListener( + getRequestListenerForRequest(imageRequest, requestListener), requestListener2) + callerContextVerifier?.verifyCallerContext(callerContext, true) + return try { + val lowestPermittedRequestLevel = + RequestLevel.getMax( + imageRequest.lowestPermittedRequestLevel, lowestPermittedRequestLevelOnSubmit) + val settableProducerContext = + SettableProducerContext( + imageRequest, + generateUniqueFutureId(), + requestListener2, + callerContext, + lowestPermittedRequestLevel, /* isPrefetch */ + true, + config.experiments?.allowProgressiveOnPrefetch == true && + imageRequest.progressiveRenderingEnabled, + priority, + config) + create(producerSequence, settableProducerContext, requestListener2) + } catch (exception: Exception) { + DataSources.immediateFailedDataSource(exception) + } + } + + fun getRequestListenerForRequest( + imageRequest: ImageRequest?, + requestListener: RequestListener? + ): RequestListener = + if (requestListener == null) { + if (imageRequest!!.requestListener == null) { + this.requestListener + } else { + ForwardingRequestListener(this.requestListener, imageRequest.requestListener) + } + } else { + if (imageRequest!!.requestListener == null) { + ForwardingRequestListener(this.requestListener, requestListener) + } else { + ForwardingRequestListener( + this.requestListener, requestListener, imageRequest.requestListener) + } + } + + fun getCombinedRequestListener(listener: RequestListener?): RequestListener = + listener?.let { ForwardingRequestListener(requestListener, it) } ?: requestListener + + private fun predicateForUri(uri: Uri): Predicate = Predicate { key -> + key.containsUri(uri) + } + + fun pause() { + threadHandoffProducerQueue.startQueueing() + } + + fun resume() { + threadHandoffProducerQueue.stopQueuing() + } + + val isPaused: Boolean + get() = threadHandoffProducerQueue.isQueueing + + init { + + this.bitmapMemoryCache = bitmapMemoryCache + this.encodedMemoryCache = encodedMemoryCache + this.mainBufferedDiskCache = mainBufferedDiskCache + this.smallImageBufferedDiskCache = smallImageBufferedDiskCache + this.cacheKeyFactory = cacheKeyFactory + this.threadHandoffProducerQueue = threadHandoffProducerQueue + this.suppressBitmapPrefetchingSupplier = suppressBitmapPrefetchingSupplier + isLazyDataSource = lazyDataSource + this.callerContextVerifier = callerContextVerifier + this.config = config + } + + fun init() { + // Yes, this does nothing. It's a placeholder method to be used in locations where + // an injection would otherwise appear to be unused. + } + + companion object { + private val PREFETCH_EXCEPTION = CancellationException("Prefetching is not enabled") + } +} diff --git a/imagepipeline/src/main/java/com/facebook/imagepipeline/datasource/BaseBitmapReferenceDataSubscriber.java b/imagepipeline/src/main/java/com/facebook/imagepipeline/datasource/BaseBitmapReferenceDataSubscriber.java index 4ad071d9c4..eb6e503284 100644 --- a/imagepipeline/src/main/java/com/facebook/imagepipeline/datasource/BaseBitmapReferenceDataSubscriber.java +++ b/imagepipeline/src/main/java/com/facebook/imagepipeline/datasource/BaseBitmapReferenceDataSubscriber.java @@ -15,7 +15,6 @@ import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.image.CloseableStaticBitmap; import com.facebook.infer.annotation.Nullsafe; -import javax.annotation.Nonnull; import javax.annotation.Nullable; /** @@ -49,7 +48,7 @@ public abstract class BaseBitmapReferenceDataSubscriber extends BaseDataSubscriber> { @Override - public void onNewResultImpl(@Nonnull DataSource> dataSource) { + public void onNewResultImpl(DataSource> dataSource) { if (!dataSource.isFinished()) { return; } diff --git a/imagepipeline/src/main/java/com/facebook/imagepipeline/listener/ForwardingRequestListener2.kt b/imagepipeline/src/main/java/com/facebook/imagepipeline/listener/ForwardingRequestListener2.kt index 700dda82ae..6114cd9cef 100644 --- a/imagepipeline/src/main/java/com/facebook/imagepipeline/listener/ForwardingRequestListener2.kt +++ b/imagepipeline/src/main/java/com/facebook/imagepipeline/listener/ForwardingRequestListener2.kt @@ -10,13 +10,16 @@ package com.facebook.imagepipeline.listener import com.facebook.common.logging.FLog import com.facebook.imagepipeline.producers.ProducerContext import java.util.ArrayList -import java.util.Set class ForwardingRequestListener2 : RequestListener2 { - private var requestListeners: MutableList + private val requestListeners: MutableList - constructor(listenersToAdd: Set) { + constructor(listenersToAdd: Set?) { + if (listenersToAdd == null) { + requestListeners = mutableListOf() + return + } requestListeners = ArrayList(listenersToAdd.size) listenersToAdd.filterNotNullTo(requestListeners) } diff --git a/imagepipeline/src/test/java/com/facebook/imagepipeline/core/ImagePipelineTest.java b/imagepipeline/src/test/java/com/facebook/imagepipeline/core/ImagePipelineTest.java index 6cd24d52c9..27db8402da 100644 --- a/imagepipeline/src/test/java/com/facebook/imagepipeline/core/ImagePipelineTest.java +++ b/imagepipeline/src/test/java/com/facebook/imagepipeline/core/ImagePipelineTest.java @@ -13,7 +13,6 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.anyObject; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -22,6 +21,7 @@ import android.net.Uri; import com.facebook.cache.common.CacheKey; +import com.facebook.cache.common.DebuggingCacheKey; import com.facebook.cache.common.MultiCacheKey; import com.facebook.cache.common.SimpleCacheKey; import com.facebook.common.internal.Predicate; @@ -56,11 +56,13 @@ /** Tests for ImagePipeline */ @RunWith(RobolectricTestRunner.class) public class ImagePipelineTest { + @Mock public ImageRequest mImageRequest; @Mock public ProducerSequenceFactory mProducerSequenceFactory; @Mock public CacheKeyFactory mCacheKeyFactory; @Mock public Object mCallerContext; @Mock public ImagePipelineConfigInterface mConfig; + @Mock public ImagePipelineExperiments mImagePipelineExperiments; private Supplier mPrefetchEnabledSupplier; private Supplier mSuppressBitmapPrefetchingSupplier; @@ -112,6 +114,9 @@ public void setUp() throws Exception { when(mImageRequest.getLowestPermittedRequestLevel()) .thenReturn(ImageRequest.RequestLevel.FULL_FETCH); when(mImageRequest.shouldDecodePrefetches()).thenReturn(null); + + when(mConfig.getExperiments()).thenReturn(mImagePipelineExperiments); + when(mImagePipelineExperiments.getPrefetchShortcutEnabled()).thenReturn(false); } @Test @@ -499,14 +504,20 @@ public void testClearMemoryCaches() { @Test public void testIsInDiskCacheFromMainDiskCache() { when(mImageRequest.getCacheChoice()).thenReturn(ImageRequest.CacheChoice.DEFAULT); - when(mMainDiskStorageCache.diskCheckSync(isNull(CacheKey.class))).thenReturn(true); + CacheKey cacheKey = mock(DebuggingCacheKey.class); + when(mMainDiskStorageCache.diskCheckSync(cacheKey)).thenReturn(true); + when(mCacheKeyFactory.getEncodedCacheKey(any(ImageRequest.class), anyObject())) + .thenReturn(cacheKey); assertTrue(mImagePipeline.isInDiskCacheSync(mImageRequest)); } @Test public void testIsInDiskCacheFromSmallDiskCache() { when(mImageRequest.getCacheChoice()).thenReturn(ImageRequest.CacheChoice.SMALL); - when(mSmallImageDiskStorageCache.diskCheckSync(isNull(CacheKey.class))).thenReturn(true); + CacheKey cacheKey = mock(DebuggingCacheKey.class); + when(mSmallImageDiskStorageCache.diskCheckSync(cacheKey)).thenReturn(true); + when(mCacheKeyFactory.getEncodedCacheKey(any(ImageRequest.class), anyObject())) + .thenReturn(cacheKey); assertTrue(mImagePipeline.isInDiskCacheSync(mImageRequest)); } diff --git a/vito/core-java-impl/src/main/java/com/facebook/fresco/vito/core/impl/FrescoVitoPrefetcherImpl.kt b/vito/core-java-impl/src/main/java/com/facebook/fresco/vito/core/impl/FrescoVitoPrefetcherImpl.kt index c2c9a59cf8..02a86d43ea 100644 --- a/vito/core-java-impl/src/main/java/com/facebook/fresco/vito/core/impl/FrescoVitoPrefetcherImpl.kt +++ b/vito/core-java-impl/src/main/java/com/facebook/fresco/vito/core/impl/FrescoVitoPrefetcherImpl.kt @@ -37,7 +37,7 @@ class FrescoVitoPrefetcherImpl( imageOptions: ImageOptions?, callerContext: Any?, callsite: String - ): DataSource { + ): DataSource { return when (prefetchTarget) { PrefetchTarget.MEMORY_DECODED -> prefetchToBitmapCache(uri, imageOptions, callerContext, callsite) @@ -54,7 +54,7 @@ class FrescoVitoPrefetcherImpl( imageOptions: DecodedImageOptions?, callerContext: Any?, callsite: String - ): DataSource { + ): DataSource { val imageRequest = imagePipelineUtils.buildImageRequest(uri, imageOptions ?: defaults()) return prefetch(PrefetchTarget.MEMORY_DECODED, imageRequest, callerContext, null) } @@ -64,7 +64,7 @@ class FrescoVitoPrefetcherImpl( imageOptions: EncodedImageOptions?, callerContext: Any?, callsite: String - ): DataSource { + ): DataSource { val imageRequest = imagePipelineUtils.buildEncodedImageRequest(uri, imageOptions ?: defaults()) return prefetch(PrefetchTarget.MEMORY_ENCODED, imageRequest, callerContext, null) } @@ -74,7 +74,7 @@ class FrescoVitoPrefetcherImpl( imageOptions: ImageOptions?, callerContext: Any?, callsite: String - ): DataSource { + ): DataSource { val imageRequest = imagePipelineUtils.buildEncodedImageRequest(uri, imageOptions ?: defaults()) return prefetch(PrefetchTarget.DISK, imageRequest, callerContext, null) } @@ -85,7 +85,7 @@ class FrescoVitoPrefetcherImpl( callerContext: Any?, requestListener: RequestListener?, callsite: String - ): DataSource = + ): DataSource = prefetch(prefetchTarget, imageRequest.finalImageRequest, callerContext, requestListener) private fun prefetch( @@ -93,7 +93,7 @@ class FrescoVitoPrefetcherImpl( imageRequest: ImageRequest?, callerContext: Any?, requestListener: RequestListener? - ): DataSource { + ): DataSource { callerContextVerifier?.verifyCallerContext(callerContext, false) return if (imageRequest == null) { DataSources.immediateFailedDataSource(NULL_IMAGE_MESSAGE) diff --git a/vito/core-java-impl/src/main/java/com/facebook/fresco/vito/core/impl/NoOpFrescoVitoPrefetcher.kt b/vito/core-java-impl/src/main/java/com/facebook/fresco/vito/core/impl/NoOpFrescoVitoPrefetcher.kt index a53dde5c97..eab3bef656 100644 --- a/vito/core-java-impl/src/main/java/com/facebook/fresco/vito/core/impl/NoOpFrescoVitoPrefetcher.kt +++ b/vito/core-java-impl/src/main/java/com/facebook/fresco/vito/core/impl/NoOpFrescoVitoPrefetcher.kt @@ -26,28 +26,28 @@ class NoOpFrescoVitoPrefetcher : FrescoVitoPrefetcher { imageOptions: ImageOptions?, callerContext: Any?, callsite: String - ): DataSource = throwUnsupportedOperationException() + ): DataSource = throwUnsupportedOperationException() override fun prefetchToBitmapCache( uri: Uri, imageOptions: DecodedImageOptions?, callerContext: Any?, callsite: String - ): DataSource = throwUnsupportedOperationException() + ): DataSource = throwUnsupportedOperationException() override fun prefetchToEncodedCache( uri: Uri, imageOptions: EncodedImageOptions?, callerContext: Any?, callsite: String - ): DataSource = throwUnsupportedOperationException() + ): DataSource = throwUnsupportedOperationException() override fun prefetchToDiskCache( uri: Uri, imageOptions: ImageOptions?, callerContext: Any?, callsite: String - ): DataSource = throwUnsupportedOperationException() + ): DataSource = throwUnsupportedOperationException() override fun prefetch( prefetchTarget: PrefetchTarget, @@ -55,7 +55,7 @@ class NoOpFrescoVitoPrefetcher : FrescoVitoPrefetcher { callerContext: Any?, requestListener: RequestListener?, callsite: String - ): DataSource = throwUnsupportedOperationException() + ): DataSource = throwUnsupportedOperationException() override fun setDistanceToViewport( distance: Int, diff --git a/vito/core/src/main/java/com/facebook/fresco/vito/core/FrescoVitoPrefetcher.kt b/vito/core/src/main/java/com/facebook/fresco/vito/core/FrescoVitoPrefetcher.kt index c3d0278d83..51744f4191 100644 --- a/vito/core/src/main/java/com/facebook/fresco/vito/core/FrescoVitoPrefetcher.kt +++ b/vito/core/src/main/java/com/facebook/fresco/vito/core/FrescoVitoPrefetcher.kt @@ -35,7 +35,7 @@ interface FrescoVitoPrefetcher { imageOptions: ImageOptions?, callerContext: Any?, callsite: String - ): DataSource + ): DataSource /** * Prefetch an image to the bitmap memory cache (for decoded images). In order to cancel the @@ -55,7 +55,7 @@ interface FrescoVitoPrefetcher { imageOptions: DecodedImageOptions?, callerContext: Any?, callsite: String - ): DataSource + ): DataSource /** * Prefetch an image to the encoded memory cache. In order to cancel the prefetch, close the @@ -75,7 +75,7 @@ interface FrescoVitoPrefetcher { imageOptions: EncodedImageOptions?, callerContext: Any?, callsite: String - ): DataSource + ): DataSource /** * Prefetch an image to the disk cache. In order to cancel the prefetch, close the [DataSource] @@ -95,7 +95,7 @@ interface FrescoVitoPrefetcher { imageOptions: ImageOptions?, callerContext: Any?, callsite: String - ): DataSource + ): DataSource /** * Prefetch an image to the given [PrefetchTarget] using a [VitoImageRequest]. In order to cancel @@ -116,7 +116,7 @@ interface FrescoVitoPrefetcher { callerContext: Any?, requestListener: RequestListener?, callsite: String - ): DataSource + ): DataSource /** * Sets the image's relative distance to the viewport for the purpose of prioritization. diff --git a/vito/litho/src/main/java/com/facebook/fresco/vito/litho/FrescoVitoImage2Spec.kt b/vito/litho/src/main/java/com/facebook/fresco/vito/litho/FrescoVitoImage2Spec.kt index be398f49c7..07a861cb5a 100644 --- a/vito/litho/src/main/java/com/facebook/fresco/vito/litho/FrescoVitoImage2Spec.kt +++ b/vito/litho/src/main/java/com/facebook/fresco/vito/litho/FrescoVitoImage2Spec.kt @@ -83,7 +83,7 @@ object FrescoVitoImage2Spec { @OnCreateInitialState fun onCreateInitialState( context: ComponentContext, - workingRangePrefetchData: StateValue>>, + workingRangePrefetchData: StateValue>>, ) { if (FrescoVitoProvider.hasBeenInitialized() && FrescoVitoProvider.getConfig().prefetchConfig.prefetchWithWorkingRange()) { @@ -146,7 +146,7 @@ object FrescoVitoImage2Spec { @Prop(optional = true) prefetch: Prefetch?, @Prop(optional = true) prefetchRequestListener: RequestListener?, @CachedValue requestCachedValue: VitoImageRequest?, - prefetchDataSource: Output>, + prefetchDataSource: Output>, ) { if (requestCachedValue == null) { return @@ -177,7 +177,7 @@ object FrescoVitoImage2Spec { @FromBoundsDefined requestFromBoundsDefined: VitoImageRequest?, @FromPrepare prefetchDataSource: DataSource?, @FromBoundsDefined viewportDimensions: Rect, - @State workingRangePrefetchData: AtomicReference>?, + @State workingRangePrefetchData: AtomicReference>?, @TreeProp contextChain: ContextChain?, ) { val request = requestCachedValue ?: requestFromBoundsDefined @@ -224,7 +224,7 @@ object FrescoVitoImage2Spec { @FromBoundsDefined requestFromBoundsDefined: VitoImageRequest?, @FromPrepare prefetchDataSource: DataSource?, @FromBoundsDefined viewportDimensions: Rect, - @State workingRangePrefetchData: AtomicReference>?, + @State workingRangePrefetchData: AtomicReference>?, ) { val request = requestCachedValue ?: requestFromBoundsDefined // We fetch in both mount and bind in case an unbind event triggered a delayed release. @@ -250,7 +250,7 @@ object FrescoVitoImage2Spec { fun onUnbind( c: ComponentContext, frescoDrawable: FrescoDrawableInterface, - @FromPrepare prefetchDataSource: DataSource?, + @FromPrepare prefetchDataSource: DataSource?, ) { frescoDrawable.imagePerfListener.onImageUnbind(frescoDrawable) if (FrescoVitoProvider.getConfig().useBindOnly()) { @@ -266,7 +266,7 @@ object FrescoVitoImage2Spec { fun onUnmount( c: ComponentContext, frescoDrawable: FrescoDrawableInterface, - @FromPrepare prefetchDataSource: DataSource?, + @FromPrepare prefetchDataSource: DataSource?, ) { frescoDrawable.imagePerfListener.onImageUnmount(frescoDrawable) if (FrescoVitoProvider.getConfig().useBindOnly()) { @@ -338,8 +338,8 @@ object FrescoVitoImage2Spec { @Prop(optional = true) prefetch: Prefetch?, @Prop(optional = true) callerContext: Any?, @CachedValue requestCachedValue: VitoImageRequest?, - @FromPrepare prefetchDataSource: DataSource?, - @State workingRangePrefetchData: AtomicReference>?, + @FromPrepare prefetchDataSource: DataSource?, + @State workingRangePrefetchData: AtomicReference>?, ) { if (requestCachedValue == null || workingRangePrefetchData == null) { return @@ -366,7 +366,7 @@ object FrescoVitoImage2Spec { @OnExitedRange(name = "imagePrefetch") fun onExitedWorkingRange( c: ComponentContext, - @State workingRangePrefetchData: AtomicReference>?, + @State workingRangePrefetchData: AtomicReference>?, ) { cancelWorkingRangePrefetch(workingRangePrefetchData) } @@ -479,7 +479,7 @@ object FrescoVitoImage2Spec { } @JvmStatic - fun cancelWorkingRangePrefetch(prefetchData: AtomicReference>?) { + fun cancelWorkingRangePrefetch(prefetchData: AtomicReference>?) { if (prefetchData == null) { return }