/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.imagepipeline.core; import java.util.Map; import android.net.Uri; import android.os.Build; import android.util.Pair; import com.facebook.common.internal.Maps; import com.facebook.common.internal.Preconditions; import com.facebook.common.internal.VisibleForTesting; import com.facebook.common.media.MediaUtils; import com.facebook.common.references.CloseableReference; import com.facebook.common.util.UriUtil; import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.memory.PooledByteBuffer; import com.facebook.imagepipeline.producers.AddImageTransformMetaDataProducer; import com.facebook.imagepipeline.producers.BitmapMemoryCacheKeyMultiplexProducer; import com.facebook.imagepipeline.producers.BitmapMemoryCacheProducer; import com.facebook.imagepipeline.producers.BranchOnSeparateImagesProducer; import com.facebook.imagepipeline.producers.DecodeProducer; import com.facebook.imagepipeline.producers.EncodedMemoryCacheProducer; import com.facebook.imagepipeline.producers.ImageTransformMetaData; import com.facebook.imagepipeline.producers.LocalAssetFetchProducer; import com.facebook.imagepipeline.producers.LocalContentUriFetchProducer; import com.facebook.imagepipeline.producers.LocalExifThumbnailProducer; import com.facebook.imagepipeline.producers.LocalFileFetchProducer; import com.facebook.imagepipeline.producers.LocalResourceFetchProducer; import com.facebook.imagepipeline.producers.LocalVideoThumbnailProducer; import com.facebook.imagepipeline.producers.NetworkFetchProducer; import com.facebook.imagepipeline.producers.PostprocessorProducer; import com.facebook.imagepipeline.producers.Producer; import com.facebook.imagepipeline.producers.ResizeAndRotateProducer; import com.facebook.imagepipeline.producers.SwallowResultProducer; import com.facebook.imagepipeline.producers.ThreadHandoffProducer; import com.facebook.imagepipeline.producers.ThrottlingProducer; import com.facebook.imagepipeline.request.ImageRequest; public class ProducerSequenceFactory { private static final int MAX_SIMULTANEOUS_FILE_FETCH_AND_RESIZE = 5; private final ProducerFactory mProducerFactory; private final NetworkFetchProducer mNetworkFetchProducer; private final boolean mResizeAndRotateEnabledForNetwork; // Saved sequences @VisibleForTesting Producer<CloseableReference<CloseableImage>> mBitmapCacheGetOnlySequence; @VisibleForTesting Producer<CloseableReference<CloseableImage>> mNetworkFetchSequence; @VisibleForTesting Producer<CloseableReference<PooledByteBuffer>> mBackgroundNetworkFetchToEncodedMemorySequence; @VisibleForTesting Producer<Void> mNetworkFetchToEncodedMemoryPrefetchSequence; private Producer<CloseableReference<PooledByteBuffer>> mCommonNetworkFetchToEncodedMemorySequence; @VisibleForTesting Producer<CloseableReference<CloseableImage>> mLocalImageFileFetchSequence; @VisibleForTesting Producer<CloseableReference<CloseableImage>> mLocalVideoFileFetchSequence; @VisibleForTesting Producer<CloseableReference<CloseableImage>> mLocalContentUriFetchSequence; @VisibleForTesting Producer<CloseableReference<CloseableImage>> mLocalResourceFetchSequence; @VisibleForTesting Producer<CloseableReference<CloseableImage>> mLocalAssetFetchSequence; @VisibleForTesting Map< Producer<CloseableReference<CloseableImage>>, Producer<CloseableReference<CloseableImage>>> mPostprocessorSequences; @VisibleForTesting Map<Producer<CloseableReference<CloseableImage>>, Producer<Void>> mCloseableImagePrefetchSequences; public ProducerSequenceFactory( ProducerFactory producerFactory, NetworkFetchProducer networkFetchProducer, boolean resizeAndRotateEnabledForNetwork) { mProducerFactory = producerFactory; mNetworkFetchProducer = networkFetchProducer; mResizeAndRotateEnabledForNetwork = resizeAndRotateEnabledForNetwork; mPostprocessorSequences = Maps.newHashMap(); mCloseableImagePrefetchSequences = Maps.newHashMap(); } /** * Returns a sequence that can be used for a request for an encoded image. * * @param imageRequest the request that will be submitted * @return the sequence that should be used to process the request */ public Producer<CloseableReference<PooledByteBuffer>> getEncodedImageProducerSequence( ImageRequest imageRequest) { validateEncodedImageRequest(imageRequest); return getBackgroundNetworkFetchToEncodedMemorySequence(); } /** * Returns a sequence that can be used for a prefetch request for an encoded image. * * <p>Guaranteed to return the same sequence as * {@code getEncodedImageProducerSequence(request)}, except that it is pre-pended with a * {@link SwallowResultProducer}. * @param imageRequest the request that will be submitted * @return the sequence that should be used to process the request */ public Producer<Void> getEncodedImagePrefetchProducerSequence(ImageRequest imageRequest) { validateEncodedImageRequest(imageRequest); return getNetworkFetchToEncodedMemoryPrefetchSequence(); } private static void validateEncodedImageRequest(ImageRequest imageRequest) { Preconditions.checkNotNull(imageRequest); Preconditions.checkArgument(UriUtil.isNetworkUri(imageRequest.getSourceUri())); Preconditions.checkArgument( imageRequest.getLowestPermittedRequestLevel() == ImageRequest.RequestLevel.FULL_FETCH); } /** * Returns a sequence that can be used for a request that just requires a bitmap cache lookup. * * <p> Sequence is: bitmap cache get -> null producer * @return the sequence that should be used to process the request. */ public synchronized Producer<CloseableReference<CloseableImage>> getBitmapCacheGetOnlySequence() { if (mBitmapCacheGetOnlySequence == null) { mBitmapCacheGetOnlySequence = mProducerFactory.newBitmapMemoryCacheGetProducer( mProducerFactory.<CloseableReference<CloseableImage>>newNullProducer()); } return mBitmapCacheGetOnlySequence; } /** * Returns a sequence that can be used for a request for a decoded image. * * @param imageRequest the request that will be submitted * @return the sequence that should be used to process the request */ public Producer<CloseableReference<CloseableImage>> getDecodedImageProducerSequence( ImageRequest imageRequest) { Producer<CloseableReference<CloseableImage>> pipelineSequence = getBasicDecodedImageSequence(imageRequest); if (imageRequest.getPostprocessor() != null) { return getPostprocessorSequence(pipelineSequence); } else { return pipelineSequence; } } /** * Returns a sequence that can be used for a prefetch request for a decoded image. * * @param imageRequest the request that will be submitted * @return the sequence that should be used to process the request */ public Producer<Void> getDecodedImagePrefetchProducerSequence( ImageRequest imageRequest) { return getDecodedImagePrefetchSequence(getBasicDecodedImageSequence(imageRequest)); } private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequence( ImageRequest imageRequest) { Preconditions.checkNotNull(imageRequest); ImageRequest.RequestLevel lowestPermittedRequestLevel = imageRequest.getLowestPermittedRequestLevel(); Preconditions.checkState( lowestPermittedRequestLevel.equals(ImageRequest.RequestLevel.FULL_FETCH) || lowestPermittedRequestLevel.equals( ImageRequest.RequestLevel.BITMAP_MEMORY_CACHE), "Only support bitmap memory cache or full fetch at present, request level is %s ", lowestPermittedRequestLevel); if (lowestPermittedRequestLevel.equals(ImageRequest.RequestLevel.BITMAP_MEMORY_CACHE)) { return getBitmapCacheGetOnlySequence(); } Uri uri = imageRequest.getSourceUri(); if (UriUtil.isNetworkUri(uri)) { return getNetworkFetchSequence(); } else if (UriUtil.isLocalFileUri(uri)) { if (MediaUtils.isVideo(MediaUtils.extractMime(uri.getPath()))) { return getLocalVideoFileFetchSequence(); } else { return getLocalImageFileFetchSequence(); } } else if (UriUtil.isLocalContentUri(uri)) { return getLocalContentUriFetchSequence(); } else if (UriUtil.isLocalAssetUri(uri)) { return getLocalAssetFetchSequence(); } else if (UriUtil.isLocalResourceUri(uri)) { return getLocalResourceFetchSequence(); } else { throw new RuntimeException( "Unsupported image type! Uri is: " + uri.toString().substring(0, 30)); } } /** * swallow result if prefetch -> bitmap cache get -> wait if scrolling -> * background thread hand-off -> multiplex -> bitmap cache -> decode -> multiplex -> * encoded cache -> disk cache -> (webp transcode) -> network fetch. */ private synchronized Producer<CloseableReference<CloseableImage>> getNetworkFetchSequence() { if (mNetworkFetchSequence == null) { mNetworkFetchSequence = newBitmapCacheGetToDecodeSequence(getCommonNetworkFetchToEncodedMemorySequence()); } return mNetworkFetchSequence; } /** * background-thread hand-off -> multiplex -> encoded cache -> * disk cache -> (webp transcode) -> network fetch. */ private synchronized Producer<CloseableReference<PooledByteBuffer>> getBackgroundNetworkFetchToEncodedMemorySequence() { if (mBackgroundNetworkFetchToEncodedMemorySequence == null) { // Use hand-off producer to ensure that we don't do any unnecessary work on the UI thread. mBackgroundNetworkFetchToEncodedMemorySequence = mProducerFactory.newBackgroundThreadHandoffProducer( getCommonNetworkFetchToEncodedMemorySequence()); } return mBackgroundNetworkFetchToEncodedMemorySequence; } /** * swallow-result -> background-thread hand-off -> multiplex -> encoded cache -> * disk cache -> (webp transcode) -> network fetch. */ private synchronized Producer<Void> getNetworkFetchToEncodedMemoryPrefetchSequence() { if (mNetworkFetchToEncodedMemoryPrefetchSequence == null) { mNetworkFetchToEncodedMemoryPrefetchSequence = mProducerFactory.newSwallowResultProducer( getBackgroundNetworkFetchToEncodedMemorySequence()); } return mNetworkFetchToEncodedMemoryPrefetchSequence; } /** * multiplex -> encoded cache -> disk cache -> (webp transcode) -> network fetch. */ private synchronized Producer<CloseableReference<PooledByteBuffer>> getCommonNetworkFetchToEncodedMemorySequence() { if (mCommonNetworkFetchToEncodedMemorySequence == null) { mCommonNetworkFetchToEncodedMemorySequence = newEncodedCacheMultiplexToTranscodeSequence(mNetworkFetchProducer, /* isLocal */false); if (mResizeAndRotateEnabledForNetwork) { mCommonNetworkFetchToEncodedMemorySequence = newResizeAndRotateImagesSequence(mCommonNetworkFetchToEncodedMemorySequence); } } return mCommonNetworkFetchToEncodedMemorySequence; } /** * bitmap cache get -> wait if scrolling -> * background thread hand-off -> multiplex -> bitmap cache -> decode -> * branch on separate images * -> exif resize and rotate -> exif thumbnail creation * -> local image resize and rotate -> add meta data producer -> multiplex -> encoded cache -> * (webp transcode) -> local file fetch. */ private synchronized Producer<CloseableReference<CloseableImage>> getLocalImageFileFetchSequence() { if (mLocalImageFileFetchSequence == null) { LocalFileFetchProducer localFileFetchProducer = mProducerFactory.newLocalFileFetchProducer(); mLocalImageFileFetchSequence = newBitmapCacheGetToTranscodeSequence(localFileFetchProducer, /* isLocal */true); } return mLocalImageFileFetchSequence; } /** * Bitmap cache get -> wait if scrolling -> thread hand off -> multiplex -> bitmap cache -> * local video thumbnail */ private synchronized Producer<CloseableReference<CloseableImage>> getLocalVideoFileFetchSequence() { if (mLocalVideoFileFetchSequence == null) { LocalVideoThumbnailProducer localVideoThumbnailProducer = mProducerFactory.newLocalVideoThumbnailProducer(); mLocalVideoFileFetchSequence = newBitmapCacheGetToBitmapCacheSequence(localVideoThumbnailProducer); } return mLocalVideoFileFetchSequence; } /** * bitmap cache get -> wait if scrolling -> * background thread hand-off -> multiplex -> bitmap cache -> decode -> * branch on separate images * -> exif resize and rotate -> exif thumbnail creation * -> local image resize and rotate -> add meta data producer -> multiplex -> encoded cache -> * (webp transcode) -> local content uri fetch. */ private synchronized Producer<CloseableReference<CloseableImage>> getLocalContentUriFetchSequence() { if (mLocalContentUriFetchSequence == null) { LocalContentUriFetchProducer localContentUriFetchProducer = mProducerFactory.newContentUriFetchProducer(); mLocalContentUriFetchSequence = newBitmapCacheGetToTranscodeSequence(localContentUriFetchProducer, /* isLocal */true); } return mLocalContentUriFetchSequence; } /** * bitmap cache get -> wait if scrolling -> * background thread hand-off -> multiplex -> bitmap cache -> decode -> * branch on separate images * -> exif resize and rotate -> exif thumbnail creation * -> local image resize and rotate -> add meta data producer -> multiplex -> encoded cache -> * (webp transcode) -> local resource fetch. */ private synchronized Producer<CloseableReference<CloseableImage>> getLocalResourceFetchSequence() { if (mLocalResourceFetchSequence == null) { LocalResourceFetchProducer localResourceFetchProducer = mProducerFactory.newLocalResourceFetchProducer(); mLocalResourceFetchSequence = newBitmapCacheGetToTranscodeSequence(localResourceFetchProducer, /* isLocal */true); } return mLocalResourceFetchSequence; } /** * bitmap cache get -> wait if scrolling -> * background thread hand-off -> multiplex -> bitmap cache -> decode -> * branch on separate images * -> exif resize and rotate -> exif thumbnail creation * -> local image resize and rotate -> add meta data producer -> multiplex -> encoded cache -> * (webp transcode) -> local asset fetch. */ private synchronized Producer<CloseableReference<CloseableImage>> getLocalAssetFetchSequence() { if (mLocalAssetFetchSequence == null) { LocalAssetFetchProducer localAssetFetchProducer = mProducerFactory.newLocalAssetFetchProducer(); mLocalAssetFetchSequence = newBitmapCacheGetToTranscodeSequence(localAssetFetchProducer, /* isLocal */true); } return mLocalAssetFetchSequence; } /** * Creates a new fetch sequence that just needs the source producer. * @param nextProducer the source producer * @param isLocal whether the image source is local or not * @return the new sequence */ private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToTranscodeSequence( Producer<CloseableReference<PooledByteBuffer>> nextProducer, boolean isLocal) { Producer<CloseableReference<PooledByteBuffer>> nextProducerAfterDecode = newEncodedCacheMultiplexToTranscodeSequence(nextProducer, isLocal); if (isLocal) { nextProducerAfterDecode = newLocalTransformationsSequence(nextProducerAfterDecode); } return newBitmapCacheGetToDecodeSequence(nextProducerAfterDecode); } /** * Same as {@code newBitmapCacheGetToBitmapCacheSequence} but with an extra DecodeProducer. * @param nextProducer next producer in the sequence after decode * @return bitmap cache get to decode sequence */ private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToDecodeSequence( Producer<CloseableReference<PooledByteBuffer>> nextProducer) { DecodeProducer decodeProducer = mProducerFactory.newDecodeProducer(nextProducer); return newBitmapCacheGetToBitmapCacheSequence(decodeProducer); } /** * encoded cache multiplex -> encoded cache -> (disk cache) -> (webp transcode) * @param nextProducer next producer in the sequence * @param isLocal whether the image source is local or not * @return encoded cache multiplex to webp transcode sequence */ private Producer<CloseableReference<PooledByteBuffer>> newEncodedCacheMultiplexToTranscodeSequence( Producer<CloseableReference<PooledByteBuffer>> nextProducer, boolean isLocal) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { nextProducer = mProducerFactory.newWebpTranscodeProducer(nextProducer); } if (!isLocal) { nextProducer = mProducerFactory.newDiskCacheProducer(nextProducer); } EncodedMemoryCacheProducer encodedMemoryCacheProducer = mProducerFactory.newEncodedMemoryCacheProducer(nextProducer); return mProducerFactory.newEncodedCacheKeyMultiplexProducer(encodedMemoryCacheProducer); } /** * Bitmap cache get -> wait if scrolling -> thread hand off -> multiplex -> bitmap cache * @param nextProducer next producer in the sequence after bitmap cache * @return bitmap cache get to bitmap cache sequence */ private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToBitmapCacheSequence( Producer<CloseableReference<CloseableImage>> nextProducer) { BitmapMemoryCacheProducer bitmapMemoryCacheProducer = mProducerFactory.newBitmapMemoryCacheProducer(nextProducer); BitmapMemoryCacheKeyMultiplexProducer bitmapKeyMultiplexProducer = mProducerFactory.newBitmapMemoryCacheKeyMultiplexProducer(bitmapMemoryCacheProducer); ThreadHandoffProducer<CloseableReference<CloseableImage>> threadHandoffProducer = mProducerFactory.newBackgroundThreadHandoffProducer(bitmapKeyMultiplexProducer); return mProducerFactory.newBitmapMemoryCacheGetProducer(threadHandoffProducer); } private Producer<CloseableReference<PooledByteBuffer>> newResizeAndRotateImagesSequence( Producer<CloseableReference<PooledByteBuffer>> nextProducer) { AddImageTransformMetaDataProducer addImageTransformMetaDataProducer = ProducerFactory.newAddImageTransformMetaDataProducer(nextProducer); ResizeAndRotateProducer networkImageResizeAndRotateProducer = mProducerFactory.newResizeAndRotateProducer(addImageTransformMetaDataProducer); return ProducerFactory.newRemoveImageTransformMetaDataProducer( networkImageResizeAndRotateProducer); } /** * Remove image transform meta data -> branch on separate images * -> exif resize and rotate -> exif thumbnail creation * -> local image resize and rotate -> add meta data producer * @param nextProducer next producer in the sequence after add meta data producer * @return local transformations sequence */ private Producer<CloseableReference<PooledByteBuffer>> newLocalTransformationsSequence( Producer<CloseableReference<PooledByteBuffer>> nextProducer) { AddImageTransformMetaDataProducer addImageTransformMetaDataProducer = mProducerFactory.newAddImageTransformMetaDataProducer(nextProducer); ResizeAndRotateProducer localImageResizeAndRotateProducer = mProducerFactory.newResizeAndRotateProducer(addImageTransformMetaDataProducer); ThrottlingProducer<Pair<CloseableReference<PooledByteBuffer>, ImageTransformMetaData>> localImageThrottlingProducer = mProducerFactory.newThrottlingProducer( MAX_SIMULTANEOUS_FILE_FETCH_AND_RESIZE, localImageResizeAndRotateProducer); LocalExifThumbnailProducer localExifThumbnailProducer = mProducerFactory.newLocalExifThumbnailProducer(); ResizeAndRotateProducer exifThumbnailResizeAndRotateProducer = mProducerFactory.newResizeAndRotateProducer(localExifThumbnailProducer); BranchOnSeparateImagesProducer branchOnSeparateImagesProducer = mProducerFactory.newBranchOnSeparateImagesProducer( exifThumbnailResizeAndRotateProducer, localImageThrottlingProducer); return mProducerFactory.newRemoveImageTransformMetaDataProducer(branchOnSeparateImagesProducer); } /** * post-processor producer -> copy producer -> next producer */ private synchronized Producer<CloseableReference<CloseableImage>> getPostprocessorSequence( Producer<CloseableReference<CloseableImage>> nextProducer) { if (!mPostprocessorSequences.containsKey(nextProducer)) { PostprocessorProducer postprocessorProducer = mProducerFactory.newPostprocessorProducer(nextProducer); mPostprocessorSequences.put(nextProducer, postprocessorProducer); } return mPostprocessorSequences.get(nextProducer); } /** * swallow result producer -> next producer */ private synchronized Producer<Void> getDecodedImagePrefetchSequence( Producer<CloseableReference<CloseableImage>> nextProducer) { if (!mCloseableImagePrefetchSequences.containsKey(nextProducer)) { SwallowResultProducer<CloseableReference<CloseableImage>> swallowResultProducer = mProducerFactory.newSwallowResultProducer(nextProducer); mCloseableImagePrefetchSequences.put(nextProducer, swallowResultProducer); } return mCloseableImagePrefetchSequences.get(nextProducer); } }