/*
* 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.drawee.backends.pipeline;
import android.content.res.Resources;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import com.facebook.cache.common.CacheKey;
import com.facebook.common.internal.ImmutableList;
import com.facebook.common.internal.Objects;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.Supplier;
import com.facebook.common.logging.FLog;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawable.base.DrawableWithCaches;
import com.facebook.drawee.components.DeferredReleaser;
import com.facebook.drawee.controller.AbstractDraweeController;
import com.facebook.drawee.debug.DebugControllerOverlayDrawable;
import com.facebook.drawee.drawable.OrientedDrawable;
import com.facebook.drawee.drawable.ScaleTypeDrawable;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
import com.facebook.drawee.interfaces.DraweeHierarchy;
import com.facebook.drawee.interfaces.SettableDraweeHierarchy;
import com.facebook.imagepipeline.animated.factory.AnimatedDrawableFactory;
import com.facebook.imagepipeline.cache.MemoryCache;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.CloseableStaticBitmap;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.image.ImageInfo;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
/**
* Drawee controller that bridges the image pipeline with {@link SettableDraweeHierarchy}. <p> The
* hierarchy's actual image is set to the image(s) obtained by the provided data source. The data
* source is automatically obtained and closed based on attach / detach calls.
*/
public class PipelineDraweeController
extends AbstractDraweeController<CloseableReference<CloseableImage>, ImageInfo> {
private static final Class<?> TAG = PipelineDraweeController.class;
// Components
private final Resources mResources;
private final AnimatedDrawableFactory mAnimatedDrawableFactory;
// Global drawable factories that are set when Fresco is initialized
@Nullable
private final ImmutableList<DrawableFactory> mGlobalDrawableFactories;
private @Nullable MemoryCache<CacheKey, CloseableImage> mMemoryCache;
private CacheKey mCacheKey;
// Constant state (non-final because controllers can be reused)
private Supplier<DataSource<CloseableReference<CloseableImage>>> mDataSourceSupplier;
private boolean mDrawDebugOverlay;
// Drawable factories that are unique for a given image request
private @Nullable ImmutableList<DrawableFactory> mCustomDrawableFactories;
private final DrawableFactory mDefaultDrawableFactory = new DrawableFactory() {
@Override
public boolean supportsImageType(CloseableImage image) {
return true;
}
@Override
public Drawable createDrawable(CloseableImage closeableImage) {
if (closeableImage instanceof CloseableStaticBitmap) {
CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) closeableImage;
Drawable bitmapDrawable = new BitmapDrawable(
mResources,
closeableStaticBitmap.getUnderlyingBitmap());
if (closeableStaticBitmap.getRotationAngle() == 0 ||
closeableStaticBitmap.getRotationAngle() == EncodedImage.UNKNOWN_ROTATION_ANGLE) {
return bitmapDrawable;
} else {
return new OrientedDrawable(bitmapDrawable, closeableStaticBitmap.getRotationAngle());
}
} else if (mAnimatedDrawableFactory != null) {
return mAnimatedDrawableFactory.create(closeableImage);
}
return null;
}
};
public PipelineDraweeController(
Resources resources,
DeferredReleaser deferredReleaser,
AnimatedDrawableFactory animatedDrawableFactory,
Executor uiThreadExecutor,
MemoryCache<CacheKey, CloseableImage> memoryCache,
Supplier<DataSource<CloseableReference<CloseableImage>>> dataSourceSupplier,
String id,
CacheKey cacheKey,
Object callerContext) {
this(
resources,
deferredReleaser,
animatedDrawableFactory,
uiThreadExecutor,
memoryCache,
dataSourceSupplier,
id,
cacheKey,
callerContext,
null);
}
public PipelineDraweeController(
Resources resources,
DeferredReleaser deferredReleaser,
AnimatedDrawableFactory animatedDrawableFactory,
Executor uiThreadExecutor,
MemoryCache<CacheKey, CloseableImage> memoryCache,
Supplier<DataSource<CloseableReference<CloseableImage>>> dataSourceSupplier,
String id,
CacheKey cacheKey,
Object callerContext,
@Nullable ImmutableList<DrawableFactory> globalDrawableFactories) {
super(deferredReleaser, uiThreadExecutor, id, callerContext);
mResources = resources;
mAnimatedDrawableFactory = animatedDrawableFactory;
mMemoryCache = memoryCache;
mCacheKey = cacheKey;
mGlobalDrawableFactories = globalDrawableFactories;
init(dataSourceSupplier);
}
/**
* Initializes this controller with the new data source supplier, id and caller context. This
* allows for reusing of the existing controller instead of instantiating a new one. This method
* should be called when the controller is in detached state.
*
* @param dataSourceSupplier data source supplier
* @param id unique id for this controller
* @param callerContext tag and context for this controller
*/
public void initialize(
Supplier<DataSource<CloseableReference<CloseableImage>>> dataSourceSupplier,
String id,
CacheKey cacheKey,
Object callerContext,
@Nullable ImmutableList<DrawableFactory> customDrawableFactories) {
super.initialize(id, callerContext);
init(dataSourceSupplier);
mCacheKey = cacheKey;
setCustomDrawableFactories(customDrawableFactories);
}
public void setDrawDebugOverlay(boolean drawDebugOverlay) {
mDrawDebugOverlay = drawDebugOverlay;
}
public void setCustomDrawableFactories(
@Nullable ImmutableList<DrawableFactory> customDrawableFactories) {
mCustomDrawableFactories = customDrawableFactories;
}
private void init(Supplier<DataSource<CloseableReference<CloseableImage>>> dataSourceSupplier) {
mDataSourceSupplier = dataSourceSupplier;
maybeUpdateDebugOverlay(null);
}
protected Resources getResources() {
return mResources;
}
@Override
protected DataSource<CloseableReference<CloseableImage>> getDataSource() {
if (FLog.isLoggable(FLog.VERBOSE)) {
FLog.v(TAG, "controller %x: getDataSource", System.identityHashCode(this));
}
return mDataSourceSupplier.get();
}
@Override
protected Drawable createDrawable(CloseableReference<CloseableImage> image) {
Preconditions.checkState(CloseableReference.isValid(image));
CloseableImage closeableImage = image.get();
maybeUpdateDebugOverlay(closeableImage);
Drawable drawable = maybeCreateDrawableFromFactories(mCustomDrawableFactories, closeableImage);
if (drawable != null) {
return drawable;
}
drawable = maybeCreateDrawableFromFactories(mGlobalDrawableFactories, closeableImage);
if (drawable != null) {
return drawable;
}
drawable = mDefaultDrawableFactory.createDrawable(closeableImage);
if (drawable != null) {
return drawable;
}
throw new UnsupportedOperationException("Unrecognized image class: " + closeableImage);
}
private Drawable maybeCreateDrawableFromFactories(
@Nullable ImmutableList<DrawableFactory> drawableFactories,
CloseableImage closeableImage) {
if (drawableFactories == null) {
return null;
}
for (DrawableFactory factory : drawableFactories) {
if (factory.supportsImageType(closeableImage)) {
Drawable drawable = factory.createDrawable(closeableImage);
if (drawable != null) {
return drawable;
}
}
}
return null;
}
@Override
public void setHierarchy(@Nullable DraweeHierarchy hierarchy) {
super.setHierarchy(hierarchy);
maybeUpdateDebugOverlay(null);
}
private void maybeUpdateDebugOverlay(@Nullable CloseableImage image) {
if (!mDrawDebugOverlay) {
return;
}
Drawable controllerOverlay = getControllerOverlay();
if (controllerOverlay == null) {
controllerOverlay = new DebugControllerOverlayDrawable();
setControllerOverlay(controllerOverlay);
}
if (controllerOverlay instanceof DebugControllerOverlayDrawable) {
DebugControllerOverlayDrawable debugOverlay =
(DebugControllerOverlayDrawable) controllerOverlay;
debugOverlay.setControllerId(getId());
final DraweeHierarchy draweeHierarchy = getHierarchy();
ScaleType scaleType = null;
if (draweeHierarchy != null) {
final ScaleTypeDrawable scaleTypeDrawable =
ScalingUtils.getActiveScaleTypeDrawable(draweeHierarchy.getTopLevelDrawable());
scaleType = scaleTypeDrawable != null ? scaleTypeDrawable.getScaleType() : null;
}
debugOverlay.setScaleType(scaleType);
if (image != null) {
debugOverlay.setDimensions(image.getWidth(), image.getHeight());
debugOverlay.setImageSize(image.getSizeInBytes());
} else {
debugOverlay.reset();
}
}
}
@Override
protected ImageInfo getImageInfo(CloseableReference<CloseableImage> image) {
Preconditions.checkState(CloseableReference.isValid(image));
return image.get();
}
@Override
protected int getImageHash(@Nullable CloseableReference<CloseableImage> image) {
return (image != null) ? image.getValueHash() : 0;
}
@Override
protected void releaseImage(@Nullable CloseableReference<CloseableImage> image) {
CloseableReference.closeSafely(image);
}
@Override
protected void releaseDrawable(@Nullable Drawable drawable) {
if (drawable instanceof DrawableWithCaches) {
((DrawableWithCaches) drawable).dropCaches();
}
}
@Override
protected CloseableReference<CloseableImage> getCachedImage() {
if (mMemoryCache == null || mCacheKey == null) {
return null;
}
// We get the CacheKey
CloseableReference<CloseableImage> closeableImage = mMemoryCache.get(mCacheKey);
if (closeableImage != null && !closeableImage.get().getQualityInfo().isOfFullQuality()) {
closeableImage.close();
return null;
}
return closeableImage;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("super", super.toString())
.add("dataSourceSupplier", mDataSourceSupplier)
.toString();
}
}