summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/ui/AsyncImageView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/messaging/ui/AsyncImageView.java')
-rw-r--r--src/com/android/messaging/ui/AsyncImageView.java457
1 files changed, 0 insertions, 457 deletions
diff --git a/src/com/android/messaging/ui/AsyncImageView.java b/src/com/android/messaging/ui/AsyncImageView.java
deleted file mode 100644
index 9aaf0b1..0000000
--- a/src/com/android/messaging/ui/AsyncImageView.java
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.Nullable;
-import android.support.rastermill.FrameSequenceDrawable;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.media.BindableMediaRequest;
-import com.android.messaging.datamodel.media.GifImageResource;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.datamodel.media.ImageRequestDescriptor;
-import com.android.messaging.datamodel.media.ImageResource;
-import com.android.messaging.datamodel.media.MediaRequest;
-import com.android.messaging.datamodel.media.MediaResourceManager;
-import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.ThreadUtil;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.HashSet;
-
-/**
- * An ImageView used to asynchronously request an image from MediaResourceManager and render it.
- */
-public class AsyncImageView extends ImageView implements MediaResourceLoadListener<ImageResource> {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
- // 100ms delay before disposing the image in case the AsyncImageView is re-added to the UI
- private static final int DISPOSE_IMAGE_DELAY = 100;
-
- // AsyncImageView has a 1-1 binding relationship with an ImageRequest instance that requests
- // the image from the MediaResourceManager. Since the request is done asynchronously, we
- // want to make sure the image view is always bound to the latest image request that it
- // issues, so that when the image is loaded, the ImageRequest (which extends BindableData)
- // will be able to figure out whether the binding is still valid and whether the loaded image
- // should be delivered to the AsyncImageView via onMediaResourceLoaded() callback.
- @VisibleForTesting
- public final Binding<BindableMediaRequest<ImageResource>> mImageRequestBinding;
-
- /** True if we want the image to fade in when it loads */
- private boolean mFadeIn;
-
- /** True if we want the image to reveal (scale) when it loads. When set to true, this
- * will take precedence over {@link #mFadeIn} */
- private final boolean mReveal;
-
- // The corner radius for drawing rounded corners around bitmap. The default value is zero
- // (no rounded corners)
- private final int mCornerRadius;
- private final Path mRoundedCornerClipPath;
- private int mClipPathWidth;
- private int mClipPathHeight;
-
- // A placeholder drawable that takes the spot of the image when it's loading. The default
- // setting is null (no placeholder).
- private final Drawable mPlaceholderDrawable;
- protected ImageResource mImageResource;
- private final Runnable mDisposeRunnable = new Runnable() {
- @Override
- public void run() {
- if (mImageRequestBinding.isBound()) {
- mDetachedRequestDescriptor = (ImageRequestDescriptor)
- mImageRequestBinding.getData().getDescriptor();
- }
- unbindView();
- releaseImageResource();
- }
- };
-
- private AsyncImageViewDelayLoader mDelayLoader;
- private ImageRequestDescriptor mDetachedRequestDescriptor;
-
- public AsyncImageView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mImageRequestBinding = BindingBase.createBinding(this);
- final TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.AsyncImageView,
- 0, 0);
- mFadeIn = attr.getBoolean(R.styleable.AsyncImageView_fadeIn, true);
- mReveal = attr.getBoolean(R.styleable.AsyncImageView_reveal, false);
- mPlaceholderDrawable = attr.getDrawable(R.styleable.AsyncImageView_placeholderDrawable);
- mCornerRadius = attr.getDimensionPixelSize(R.styleable.AsyncImageView_cornerRadius, 0);
- mRoundedCornerClipPath = new Path();
-
- attr.recycle();
- }
-
- /**
- * The main entrypoint for AsyncImageView to load image resource given an ImageRequestDescriptor
- * @param descriptor the request descriptor, or null if no image should be displayed
- */
- public void setImageResourceId(@Nullable final ImageRequestDescriptor descriptor) {
- final String requestKey = (descriptor == null) ? null : descriptor.getKey();
- if (mImageRequestBinding.isBound()) {
- if (TextUtils.equals(mImageRequestBinding.getData().getKey(), requestKey)) {
- // Don't re-request the bitmap if the new request is for the same resource.
- return;
- }
- unbindView();
- }
- setImage(null);
- resetTransientViewStates();
- if (!TextUtils.isEmpty(requestKey)) {
- maybeSetupPlaceholderDrawable(descriptor);
- final BindableMediaRequest<ImageResource> imageRequest =
- descriptor.buildAsyncMediaRequest(getContext(), this);
- requestImage(imageRequest);
- }
- }
-
- /**
- * Sets a delay loader that centrally manages image request delay loading logic.
- */
- public void setDelayLoader(final AsyncImageViewDelayLoader delayLoader) {
- Assert.isTrue(mDelayLoader == null);
- mDelayLoader = delayLoader;
- }
-
- /**
- * Called by the delay loader when we can resume image loading.
- */
- public void resumeLoading() {
- Assert.notNull(mDelayLoader);
- Assert.isTrue(mImageRequestBinding.isBound());
- MediaResourceManager.get().requestMediaResourceAsync(mImageRequestBinding.getData());
- }
-
- /**
- * Setup the placeholder drawable if:
- * 1. There's an image to be loaded AND
- * 2. We are given a placeholder drawable AND
- * 3. The descriptor provided us with source width and height.
- */
- private void maybeSetupPlaceholderDrawable(final ImageRequestDescriptor descriptor) {
- if (!TextUtils.isEmpty(descriptor.getKey()) && mPlaceholderDrawable != null) {
- if (descriptor.sourceWidth != ImageRequest.UNSPECIFIED_SIZE &&
- descriptor.sourceHeight != ImageRequest.UNSPECIFIED_SIZE) {
- // Set a transparent inset drawable to the foreground so it will mimick the final
- // size of the image, and use the background to show the actual placeholder
- // drawable.
- setImageDrawable(PlaceholderInsetDrawable.fromDrawable(
- new ColorDrawable(Color.TRANSPARENT),
- descriptor.sourceWidth, descriptor.sourceHeight));
- }
- setBackground(mPlaceholderDrawable);
- }
- }
-
- protected void setImage(final ImageResource resource) {
- setImage(resource, false /* isCached */);
- }
-
- protected void setImage(final ImageResource resource, final boolean isCached) {
- // Switch reference to the new ImageResource. Make sure we release the current
- // resource and addRef() on the new resource so that the underlying bitmaps don't
- // get leaked or get recycled by the bitmap cache.
- releaseImageResource();
- // Ensure that any pending dispose runnables get removed.
- ThreadUtil.getMainThreadHandler().removeCallbacks(mDisposeRunnable);
- // The drawable may require work to get if its a static object so try to only make this call
- // once.
- final Drawable drawable = (resource != null) ? resource.getDrawable(getResources()) : null;
- if (drawable != null) {
- mImageResource = resource;
- mImageResource.addRef();
- setImageDrawable(drawable);
- if (drawable instanceof FrameSequenceDrawable) {
- ((FrameSequenceDrawable) drawable).start();
- }
-
- if (getVisibility() == VISIBLE) {
- if (mReveal) {
- setVisibility(INVISIBLE);
- UiUtils.revealOrHideViewWithAnimation(this, VISIBLE, null);
- } else if (mFadeIn && !isCached) {
- // Hide initially to avoid flash.
- setAlpha(0F);
- animate().alpha(1F).start();
- }
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- if (mImageResource instanceof GifImageResource) {
- LogUtil.v(TAG, "setImage size unknown -- it's a GIF");
- } else {
- LogUtil.v(TAG, "setImage size: " + mImageResource.getMediaSize() +
- " width: " + mImageResource.getBitmap().getWidth() +
- " heigh: " + mImageResource.getBitmap().getHeight());
- }
- }
- }
- invalidate();
- }
-
- private void requestImage(final BindableMediaRequest<ImageResource> request) {
- mImageRequestBinding.bind(request);
- if (mDelayLoader == null || !mDelayLoader.isDelayLoadingImage()) {
- MediaResourceManager.get().requestMediaResourceAsync(request);
- } else {
- mDelayLoader.registerView(this);
- }
- }
-
- @Override
- public void onMediaResourceLoaded(final MediaRequest<ImageResource> request,
- final ImageResource resource, final boolean isCached) {
- if (mImageResource != resource) {
- setImage(resource, isCached);
- }
- }
-
- @Override
- public void onMediaResourceLoadError(
- final MediaRequest<ImageResource> request, final Exception exception) {
- // Media load failed, unbind and reset bitmap to default.
- unbindView();
- setImage(null);
- }
-
- private void releaseImageResource() {
- final Drawable drawable = getDrawable();
- if (drawable instanceof FrameSequenceDrawable) {
- ((FrameSequenceDrawable) drawable).stop();
- ((FrameSequenceDrawable) drawable).destroy();
- }
- if (mImageResource != null) {
- mImageResource.release();
- mImageResource = null;
- }
- setImageDrawable(null);
- setBackground(null);
- }
-
- /**
- * Resets transient view states (eg. alpha, animations) before rebinding/reusing the view.
- */
- private void resetTransientViewStates() {
- clearAnimation();
- setAlpha(1F);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- // If it was recently removed, then cancel disposing, we're still using it.
- ThreadUtil.getMainThreadHandler().removeCallbacks(mDisposeRunnable);
-
- // When the image view gets detached and immediately re-attached, any fade-in animation
- // will be terminated, leaving the view in a semi-transparent state. Make sure we restore
- // alpha when the view is re-attached.
- if (mFadeIn) {
- setAlpha(1F);
- }
-
- // Check whether we are in a simple reuse scenario: detached from window, and reattached
- // later without rebinding. This may be done by containers such as the RecyclerView to
- // reuse the views. In this case, we would like to rebind the original image request.
- if (!mImageRequestBinding.isBound() && mDetachedRequestDescriptor != null) {
- setImageResourceId(mDetachedRequestDescriptor);
- }
- mDetachedRequestDescriptor = null;
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- // Dispose the bitmap, but if an AysncImageView is removed from the window, then quickly
- // re-added, we shouldn't dispose, so wait a short time before disposing
- ThreadUtil.getMainThreadHandler().postDelayed(mDisposeRunnable, DISPOSE_IMAGE_DELAY);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- // The base implementation does not honor the minimum sizes. We try to to honor it here.
-
- final int measuredWidth = getMeasuredWidth();
- final int measuredHeight = getMeasuredHeight();
- if (measuredWidth >= getMinimumWidth() || measuredHeight >= getMinimumHeight()) {
- // We are ok if either of the minimum sizes is honored. Note that satisfying both the
- // sizes may not be possible, depending on the aspect ratio of the image and whether
- // a maximum size has been specified. This implementation only tries to handle the case
- // where both the minimum sizes are not being satisfied.
- return;
- }
-
- if (!getAdjustViewBounds()) {
- // The base implementation is reasonable in this case. If the view bounds cannot be
- // changed, it is not possible to satisfy the minimum sizes anyway.
- return;
- }
-
- final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
- final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
- if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
- // The base implementation is reasonable in this case.
- return;
- }
-
- int width = measuredWidth;
- int height = measuredHeight;
- // Get the minimum sizes that will honor other constraints as well.
- final int minimumWidth = resolveSize(
- getMinimumWidth(), getMaxWidth(), widthMeasureSpec);
- final int minimumHeight = resolveSize(
- getMinimumHeight(), getMaxHeight(), heightMeasureSpec);
- final float aspectRatio = measuredWidth / (float) measuredHeight;
- if (aspectRatio == 0) {
- // If the image is (close to) infinitely high, there is not much we can do.
- return;
- }
-
- if (width < minimumWidth) {
- height = resolveSize((int) (minimumWidth / aspectRatio),
- getMaxHeight(), heightMeasureSpec);
- width = (int) (height * aspectRatio);
- }
-
- if (height < minimumHeight) {
- width = resolveSize((int) (minimumHeight * aspectRatio),
- getMaxWidth(), widthMeasureSpec);
- height = (int) (width / aspectRatio);
- }
-
- setMeasuredDimension(width, height);
- }
-
- private static int resolveSize(int desiredSize, int maxSize, int measureSpec) {
- final int specMode = MeasureSpec.getMode(measureSpec);
- final int specSize = MeasureSpec.getSize(measureSpec);
- switch(specMode) {
- case MeasureSpec.UNSPECIFIED:
- return Math.min(desiredSize, maxSize);
-
- case MeasureSpec.AT_MOST:
- return Math.min(Math.min(desiredSize, specSize), maxSize);
-
- default:
- Assert.fail("Unreachable");
- return specSize;
- }
- }
-
- @Override
- protected void onDraw(final Canvas canvas) {
- if (mCornerRadius > 0) {
- final int currentWidth = this.getWidth();
- final int currentHeight = this.getHeight();
- if (mClipPathWidth != currentWidth || mClipPathHeight != currentHeight) {
- final RectF rect = new RectF(0, 0, currentWidth, currentHeight);
- mRoundedCornerClipPath.reset();
- mRoundedCornerClipPath.addRoundRect(rect, mCornerRadius, mCornerRadius,
- Path.Direction.CW);
- mClipPathWidth = currentWidth;
- mClipPathHeight = currentHeight;
- }
-
- final int saveCount = canvas.getSaveCount();
- canvas.save();
- canvas.clipPath(mRoundedCornerClipPath);
- super.onDraw(canvas);
- canvas.restoreToCount(saveCount);
- } else {
- super.onDraw(canvas);
- }
- }
-
- private void unbindView() {
- if (mImageRequestBinding.isBound()) {
- mImageRequestBinding.unbind();
- if (mDelayLoader != null) {
- mDelayLoader.unregisterView(this);
- }
- }
- }
-
- /**
- * As a performance optimization, the consumer of the AsyncImageView may opt to delay loading
- * the image when it's busy doing other things (such as when a list view is scrolling). In
- * order to do this, the consumer can create a new AsyncImageViewDelayLoader instance to be
- * shared among all relevant AsyncImageViews (through setDelayLoader() method), and call
- * onStartDelayLoading() and onStopDelayLoading() to start and stop delay loading, respectively.
- */
- public static class AsyncImageViewDelayLoader {
- private boolean mShouldDelayLoad;
- private final HashSet<AsyncImageView> mAttachedViews;
-
- public AsyncImageViewDelayLoader() {
- mAttachedViews = new HashSet<AsyncImageView>();
- }
-
- private void registerView(final AsyncImageView view) {
- mAttachedViews.add(view);
- }
-
- private void unregisterView(final AsyncImageView view) {
- mAttachedViews.remove(view);
- }
-
- public boolean isDelayLoadingImage() {
- return mShouldDelayLoad;
- }
-
- /**
- * Called by the consumer of this view to delay loading images
- */
- public void onDelayLoading() {
- // Don't need to explicitly tell the AsyncImageView to stop loading since
- // ImageRequests are not cancellable.
- mShouldDelayLoad = true;
- }
-
- /**
- * Called by the consumer of this view to resume loading images
- */
- public void onResumeLoading() {
- if (mShouldDelayLoad) {
- mShouldDelayLoad = false;
-
- // Notify all attached views to resume loading.
- for (final AsyncImageView view : mAttachedViews) {
- view.resumeLoading();
- }
- mAttachedViews.clear();
- }
- }
- }
-}