/*
* 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.HashMap;
import java.util.Map;
import android.net.Uri;
import android.os.Build;
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.image.EncodedImage;
import com.facebook.imagepipeline.memory.PooledByteBuffer;
import com.facebook.imagepipeline.producers.BitmapMemoryCacheKeyMultiplexProducer;
import com.facebook.imagepipeline.producers.BitmapMemoryCacheProducer;
import com.facebook.imagepipeline.producers.DecodeProducer;
import com.facebook.imagepipeline.producers.EncodedMemoryCacheProducer;
import com.facebook.imagepipeline.producers.LocalAssetFetchProducer;
import com.facebook.imagepipeline.producers.LocalContentUriFetchProducer;
import com.facebook.imagepipeline.producers.LocalFileFetchProducer;
import com.facebook.imagepipeline.producers.LocalResourceFetchProducer;
import com.facebook.imagepipeline.producers.LocalVideoThumbnailProducer;
import com.facebook.imagepipeline.producers.NetworkFetcher;
import com.facebook.imagepipeline.producers.PostprocessedBitmapMemoryCacheProducer;
import com.facebook.imagepipeline.producers.PostprocessorProducer;
import com.facebook.imagepipeline.producers.Producer;
import com.facebook.imagepipeline.producers.RemoveImageTransformMetaDataProducer;
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 NetworkFetcher mNetworkFetcher;
private final boolean mResizeAndRotateEnabledForNetwork;
private final boolean mDownsampleEnabled;
// Saved sequences
@VisibleForTesting Producer<CloseableReference<CloseableImage>> mNetworkFetchSequence;
@VisibleForTesting Producer<EncodedImage> mBackgroundNetworkFetchToEncodedMemorySequence;
@VisibleForTesting Producer<CloseableReference<PooledByteBuffer>> mEncodedImageProducerSequence;
@VisibleForTesting Producer<Void> mNetworkFetchToEncodedMemoryPrefetchSequence;
private Producer<EncodedImage> 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 Producer<CloseableReference<CloseableImage>> mDataFetchSequence;
@VisibleForTesting Map<
Producer<CloseableReference<CloseableImage>>,
Producer<CloseableReference<CloseableImage>>>
mPostprocessorSequences;
@VisibleForTesting Map<Producer<CloseableReference<CloseableImage>>, Producer<Void>>
mCloseableImagePrefetchSequences;
public ProducerSequenceFactory(
ProducerFactory producerFactory,
NetworkFetcher networkFetcher,
boolean resizeAndRotateEnabledForNetwork,
boolean downsampleEnabled) {
mProducerFactory = producerFactory;
mNetworkFetcher = networkFetcher;
mResizeAndRotateEnabledForNetwork = resizeAndRotateEnabledForNetwork;
mDownsampleEnabled = downsampleEnabled;
mPostprocessorSequences = new HashMap<>();
mCloseableImagePrefetchSequences = new HashMap<>();
}
/**
* 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);
synchronized (this) {
if (mEncodedImageProducerSequence == null) {
mEncodedImageProducerSequence = new RemoveImageTransformMetaDataProducer(
getBackgroundNetworkFetchToEncodedMemorySequence());
}
}
return mEncodedImageProducerSequence;
}
/**
* 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().getValue() <=
ImageRequest.RequestLevel.ENCODED_MEMORY_CACHE.getValue());
}
/**
* 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);
Uri uri = imageRequest.getSourceUri();
Preconditions.checkNotNull(uri, "Uri is null.");
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 if (UriUtil.isDataUri(uri)) {
return getDataFetchSequence();
} else {
String uriString = uri.toString();
if (uriString.length() > 30) {
uriString = uriString.substring(0, 30) + "...";
}
throw new RuntimeException("Unsupported uri scheme! Uri is: " + uriString);
}
}
/**
* swallow result if prefetch -> bitmap cache get ->
* 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<EncodedImage>
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<EncodedImage> getCommonNetworkFetchToEncodedMemorySequence() {
if (mCommonNetworkFetchToEncodedMemorySequence == null) {
Producer<EncodedImage> inputProducer =
newEncodedCacheMultiplexToTranscodeSequence(
mProducerFactory.newNetworkFetchProducer(mNetworkFetcher));
mCommonNetworkFetchToEncodedMemorySequence =
ProducerFactory.newAddImageTransformMetaDataProducer(inputProducer);
if (mResizeAndRotateEnabledForNetwork && !mDownsampleEnabled) {
mCommonNetworkFetchToEncodedMemorySequence =
mProducerFactory.newResizeAndRotateProducer(
mCommonNetworkFetchToEncodedMemorySequence);
}
}
return mCommonNetworkFetchToEncodedMemorySequence;
}
/**
* bitmap cache get ->
* 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 =
newBitmapCacheGetToLocalTransformSequence(localFileFetchProducer);
}
return mLocalImageFileFetchSequence;
}
/**
* Bitmap cache get -> 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 ->
* 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 =
newBitmapCacheGetToLocalTransformSequence(localContentUriFetchProducer);
}
return mLocalContentUriFetchSequence;
}
/**
* bitmap cache get ->
* 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 =
newBitmapCacheGetToLocalTransformSequence(localResourceFetchProducer);
}
return mLocalResourceFetchSequence;
}
/**
* bitmap cache get ->
* 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 =
newBitmapCacheGetToLocalTransformSequence(localAssetFetchProducer);
}
return mLocalAssetFetchSequence;
}
/**
* bitmap cache get ->
* background thread hand-off -> bitmap cache -> decode -> resize and rotate -> (webp transcode)
* -> data fetch.
*/
private synchronized Producer<CloseableReference<CloseableImage>> getDataFetchSequence() {
if (mDataFetchSequence == null) {
Producer<EncodedImage> inputProducer = mProducerFactory.newDataFetchProducer();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
inputProducer = mProducerFactory.newWebpTranscodeProducer(inputProducer);
}
inputProducer = mProducerFactory.newAddImageTransformMetaDataProducer(inputProducer);
if (!mDownsampleEnabled) {
inputProducer = mProducerFactory.newResizeAndRotateProducer(inputProducer);
}
mDataFetchSequence = newBitmapCacheGetToDecodeSequence(inputProducer);
}
return mDataFetchSequence;
}
/**
* Creates a new fetch sequence that just needs the source producer.
* @param inputProducer the source producer
* @return the new sequence
*/
private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToLocalTransformSequence(
Producer<EncodedImage> inputProducer) {
inputProducer = newEncodedCacheMultiplexToTranscodeSequence(inputProducer);
Producer<EncodedImage> inputProducerAfterDecode =
newLocalTransformationsSequence(inputProducer);
return newBitmapCacheGetToDecodeSequence(inputProducerAfterDecode);
}
/**
* Same as {@code newBitmapCacheGetToBitmapCacheSequence} but with an extra DecodeProducer.
* @param inputProducer producer providing the input to the decode
* @return bitmap cache get to decode sequence
*/
private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToDecodeSequence(
Producer<EncodedImage> inputProducer) {
DecodeProducer decodeProducer = mProducerFactory.newDecodeProducer(inputProducer);
return newBitmapCacheGetToBitmapCacheSequence(decodeProducer);
}
/**
* encoded cache multiplex -> encoded cache -> (disk cache) -> (webp transcode)
* @param inputProducer producer providing the input to the transcode
* @return encoded cache multiplex to webp transcode sequence
*/
private Producer<EncodedImage> newEncodedCacheMultiplexToTranscodeSequence(
Producer<EncodedImage> inputProducer) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
inputProducer = mProducerFactory.newWebpTranscodeProducer(inputProducer);
}
inputProducer = mProducerFactory.newDiskCacheProducer(inputProducer);
EncodedMemoryCacheProducer encodedMemoryCacheProducer =
mProducerFactory.newEncodedMemoryCacheProducer(inputProducer);
return mProducerFactory.newEncodedCacheKeyMultiplexProducer(encodedMemoryCacheProducer);
}
/**
* Bitmap cache get -> thread hand off -> multiplex -> bitmap cache
* @param inputProducer producer providing the input to the bitmap cache
* @return bitmap cache get to bitmap cache sequence
*/
private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToBitmapCacheSequence(
Producer<CloseableReference<CloseableImage>> inputProducer) {
BitmapMemoryCacheProducer bitmapMemoryCacheProducer =
mProducerFactory.newBitmapMemoryCacheProducer(inputProducer);
BitmapMemoryCacheKeyMultiplexProducer bitmapKeyMultiplexProducer =
mProducerFactory.newBitmapMemoryCacheKeyMultiplexProducer(bitmapMemoryCacheProducer);
ThreadHandoffProducer<CloseableReference<CloseableImage>> threadHandoffProducer =
mProducerFactory.newBackgroundThreadHandoffProducer(bitmapKeyMultiplexProducer);
return mProducerFactory.newBitmapMemoryCacheGetProducer(threadHandoffProducer);
}
/**
* Branch on separate images
* -> exif resize and rotate -> exif thumbnail creation
* -> local image resize and rotate -> add meta data producer
* @param inputProducer producer providing the input to add meta data producer
* @return local transformations sequence
*/
private Producer<EncodedImage> newLocalTransformationsSequence(
Producer<EncodedImage> inputProducer) {
Producer<EncodedImage> localImageProducer =
mProducerFactory.newAddImageTransformMetaDataProducer(inputProducer);
if (!mDownsampleEnabled) {
localImageProducer =
mProducerFactory.newResizeAndRotateProducer(localImageProducer);
}
ThrottlingProducer<EncodedImage>
localImageThrottlingProducer =
mProducerFactory.newThrottlingProducer(
MAX_SIMULTANEOUS_FILE_FETCH_AND_RESIZE,
localImageProducer);
Producer<EncodedImage> localExifThumbnailProducer =
mProducerFactory.newLocalExifThumbnailProducer();
if (!mDownsampleEnabled) {
localExifThumbnailProducer =
mProducerFactory.newResizeAndRotateProducer(localExifThumbnailProducer);
}
return mProducerFactory.newBranchOnSeparateImagesProducer(
localExifThumbnailProducer,
localImageThrottlingProducer);
}
/**
* post-processor producer -> copy producer -> inputProducer
*/
private synchronized Producer<CloseableReference<CloseableImage>> getPostprocessorSequence(
Producer<CloseableReference<CloseableImage>> inputProducer) {
if (!mPostprocessorSequences.containsKey(inputProducer)) {
PostprocessorProducer postprocessorProducer =
mProducerFactory.newPostprocessorProducer(inputProducer);
PostprocessedBitmapMemoryCacheProducer postprocessedBitmapMemoryCacheProducer =
mProducerFactory.newPostprocessorBitmapMemoryCacheProducer(postprocessorProducer);
mPostprocessorSequences.put(inputProducer, postprocessedBitmapMemoryCacheProducer);
}
return mPostprocessorSequences.get(inputProducer);
}
/**
* swallow result producer -> inputProducer
*/
private synchronized Producer<Void> getDecodedImagePrefetchSequence(
Producer<CloseableReference<CloseableImage>> inputProducer) {
if (!mCloseableImagePrefetchSequences.containsKey(inputProducer)) {
SwallowResultProducer<CloseableReference<CloseableImage>> swallowResultProducer =
mProducerFactory.newSwallowResultProducer(inputProducer);
mCloseableImagePrefetchSequences.put(inputProducer, swallowResultProducer);
}
return mCloseableImagePrefetchSequences.get(inputProducer);
}
}