/** * 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.react.flat; import javax.annotation.Nullable; import android.graphics.Bitmap; import com.facebook.common.executors.UiThreadImmediateExecutorService; import com.facebook.common.references.CloseableReference; import com.facebook.datasource.DataSource; import com.facebook.datasource.DataSubscriber; import com.facebook.imagepipeline.core.ImagePipeline; import com.facebook.imagepipeline.core.ImagePipelineFactory; import com.facebook.imagepipeline.image.CloseableBitmap; import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.infer.annotation.Assertions; import com.facebook.react.views.image.ImageLoadEvent; /** * Helper class for DrawImage that helps manage fetch requests through ImagePipeline. * * Request states this class can be in: * 1) mDataSource == null, mImageRef == null : request has not be started, was canceled or failed. * 2) mDataSource != null, mImageRef == null : request is in progress. * 3) mDataSource == null, mImageRef != null : request successfully finished. * 4) mDataSource != null, mImageRef != null : invalid state (should never happen) */ /* package */ final class PipelineRequestHelper implements DataSubscriber<CloseableReference<CloseableImage>> { private final ImageRequest mImageRequest; private @Nullable BitmapUpdateListener mBitmapUpdateListener; private @Nullable DataSource<CloseableReference<CloseableImage>> mDataSource; private @Nullable CloseableReference<CloseableImage> mImageRef; private int mAttachCounter; /* package */ PipelineRequestHelper(ImageRequest imageRequest) { mImageRequest = imageRequest; } /* package */ void attach(BitmapUpdateListener listener) { mBitmapUpdateListener = listener; mAttachCounter++; if (mAttachCounter != 1) { // this is a secondary attach, ignore it, only updating Bitmap boundaries if needed. Bitmap bitmap = getBitmap(); if (bitmap != null) { listener.onSecondaryAttach(bitmap); } return; } listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD_START); Assertions.assertCondition(mDataSource == null); Assertions.assertCondition(mImageRef == null); // Submit the request ImagePipeline imagePipeline = ImagePipelineFactory.getInstance().getImagePipeline(); mDataSource = imagePipeline.fetchDecodedImage(mImageRequest, RCTImageView.getCallerContext()); mDataSource.subscribe(this, UiThreadImmediateExecutorService.getInstance()); } /** * Returns whether detach() was primary, false otherwise. */ /* package */ void detach() { --mAttachCounter; if (mAttachCounter != 0) { // this is a secondary detach, ignore it return; } if (mDataSource != null) { mDataSource.close(); mDataSource = null; } if (mImageRef != null) { mImageRef.close(); mImageRef = null; } mBitmapUpdateListener = null; } /** * Returns an unsafe bitmap reference. Do not assign the result of this method to anything other * than a local variable, or it will no longer work with the reference count goes to zero. */ /* package */ @Nullable Bitmap getBitmap() { if (mImageRef == null) { return null; } CloseableImage closeableImage = mImageRef.get(); if (!(closeableImage instanceof CloseableBitmap)) { mImageRef.close(); mImageRef = null; return null; } return ((CloseableBitmap) closeableImage).getUnderlyingBitmap(); } /* package */ boolean isDetached() { return mAttachCounter == 0; } @Override public void onNewResult(DataSource<CloseableReference<CloseableImage>> dataSource) { if (!dataSource.isFinished()) { // only interested in final image, no need to close the dataSource return; } try { if (mDataSource != dataSource) { // Shouldn't ever happen, but let's be safe (dataSource got closed by callback still fired?) return; } mDataSource = null; CloseableReference<CloseableImage> imageReference = dataSource.getResult(); if (imageReference == null) { // Shouldn't ever happen, but let's be safe (dataSource got closed by callback still fired?) return; } CloseableImage image = imageReference.get(); if (!(image instanceof CloseableBitmap)) { // only bitmaps are supported imageReference.close(); return; } mImageRef = imageReference; Bitmap bitmap = getBitmap(); if (bitmap == null) { // Shouldn't ever happen, but let's be safe. return; } BitmapUpdateListener listener = Assertions.assumeNotNull(mBitmapUpdateListener); listener.onBitmapReady(bitmap); listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD); listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD_END); } finally { dataSource.close(); } } @Override public void onFailure(DataSource<CloseableReference<CloseableImage>> dataSource) { if (mDataSource == dataSource) { Assertions.assumeNotNull(mBitmapUpdateListener).onImageLoadEvent(ImageLoadEvent.ON_ERROR); Assertions.assumeNotNull(mBitmapUpdateListener).onImageLoadEvent(ImageLoadEvent.ON_LOAD_END); mDataSource = null; } dataSource.close(); } @Override public void onCancellation(DataSource<CloseableReference<CloseableImage>> dataSource) { if (mDataSource == dataSource) { mDataSource = null; } dataSource.close(); } @Override public void onProgressUpdate(DataSource<CloseableReference<CloseableImage>> dataSource) { } }