/* * Copyright (C) 2014 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.tv.settings.util; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.text.TextUtils; import android.view.View; import android.widget.ImageView; import com.android.tv.settings.widget.BitmapWorkerOptions; import com.android.tv.settings.widget.DrawableDownloader; import java.util.ArrayList; import java.util.List; /** * Initiator calls {@link #createFromImageView(ImageView)} and * {@link #writeMultipleToIntent(List, Intent)}. *

* Receiver calls {@link #readMultipleFromIntent(Context, Intent)} to read source and * {@link #createFromImageView(ImageView)} for target image view; then start animation * using {@link TransitionImageView} between these two.

* The matching of Uri is up to receiver, typically using {@link TransitionImageMatcher} *

* The transition image has three bounds, all relative to window

* - {@link #setRect(Rect)} bounds of the image view, including background color

* - {@link #setUnclippedRect(RectF)} bounds of original bitmap without clipping, the rect * might be bigger than the image view

* - {@link #setClippedRect(RectF)} bounds of clipping

*/ public class TransitionImage { private Uri mUri; private BitmapDrawable mBitmap; private final Rect mRect = new Rect(); private int mBackground = Color.TRANSPARENT; private float mAlpha = 1f; private float mSaturation = 1f; private final RectF mUnclippedRect = new RectF(); private final RectF mClippedRect = new RectF(); private boolean mUseClippedRectOnTransparent = true; public static final String EXTRA_TRANSITION_BITMAP = "com.android.tv.settings.transition_bitmap"; public static final String EXTRA_TRANSITION_BITMAP_RECT = "com.android.tv.settings.transition_bmp_rect"; public static final String EXTRA_TRANSITION_BITMAP_URI = "com.android.tv.settings.transition_bmp_uri"; public static final String EXTRA_TRANSITION_BITMAP_ALPHA = "com.android.tv.settings.transition_bmp_alpha"; public static final String EXTRA_TRANSITION_BITMAP_SATURATION = "com.android.tv.settings.transition_bmp_saturation"; public static final String EXTRA_TRANSITION_BITMAP_BACKGROUND = "com.android.tv.settings.transition_bmp_background"; public static final String EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT = "com.android.tv.settings.transition_bmp_unclipped_rect"; public static final String EXTRA_TRANSITION_BITMAP_CLIPPED_RECT = "com.android.tv.settings.transition_bmp_clipped_rect"; public static final String EXTRA_TRANSITION_MULTIPLE_BITMAP = "com.android.tv.settings.transition_multiple_bitmap"; public TransitionImage() { } public Uri getUri() { return mUri; } public void setUri(Uri uri) { mUri = uri; } public BitmapDrawable getBitmap() { return mBitmap; } public void setBitmap(BitmapDrawable bitmap) { mBitmap = bitmap; } public Rect getRect() { return mRect; } public void setRect(Rect rect) { mRect.set(rect); } public int getBackground() { return mBackground; } public void setBackground(int color) { mBackground = color; } public float getAlpha() { return mAlpha; } public void setAlpha(float alpha) { mAlpha = alpha; } public float getSaturation() { return mSaturation; } public void setSaturation(float saturation) { mSaturation = saturation; } public RectF getUnclippedRect() { return mUnclippedRect; } public void setUnclippedRect(RectF rect) { mUnclippedRect.set(rect); } public RectF getClippedRect() { return mClippedRect; } public void setClippedRect(RectF rect) { mClippedRect.set(rect); } public static List readMultipleFromIntent(Context context, Intent intent) { ArrayList transitions = new ArrayList<>(); Bundle extras = intent.getExtras(); if (extras == null) { return transitions; } TransitionImage image = new TransitionImage(); if (image.readFromBundle(context, intent.getSourceBounds(), extras)) { transitions.add(image); } Parcelable[] multiple = intent.getParcelableArrayExtra(EXTRA_TRANSITION_MULTIPLE_BITMAP); if (multiple != null) { for (int i = 0, size = multiple.length; i < size; i++) { if (!(multiple[i] instanceof Bundle)) { break; } image = new TransitionImage(); if (image.readFromBundle(context, null, (Bundle) multiple[i])) { transitions.add(image); } } } return transitions; } public static void writeMultipleToIntent(List transitions, Intent intent) { if (transitions == null || transitions.size() == 0) { return; } int size = transitions.size(); if (size == 1) { TransitionImage image = transitions.get(0); image.writeToIntent(intent); return; } Parcelable[] multipleBundle = new Parcelable[size]; for (int i = 0; i < size; i++) { Bundle b = new Bundle(); transitions.get(i).writeToBundle(b); multipleBundle[i] = b; } intent.putExtra(EXTRA_TRANSITION_MULTIPLE_BITMAP, multipleBundle); } public boolean readFromBundle(Context context, Rect intentSourceBounds, Bundle bundle) { setBitmap(null); if (bundle == null) { return false; } mUri = bundle.getParcelable(EXTRA_TRANSITION_BITMAP_URI); BitmapDrawable bitmap = null; if (mUri != null) { DrawableDownloader downloader = DrawableDownloader.getInstance(context); BitmapWorkerOptions key = new BitmapWorkerOptions.Builder(context) .resource(mUri).build(); bitmap = downloader.getLargestBitmapFromMemCache(key); } if (bitmap == null) { if (bundle.containsKey(EXTRA_TRANSITION_BITMAP)) { bitmap = new BitmapDrawable(context.getResources(), ActivityTransitionBitmapHelper.getBitmapFromBinderBundle( bundle.getBundle(EXTRA_TRANSITION_BITMAP))); } if (bitmap == null) { return false; } } Rect rect = null; String bitmapRectStr = bundle.getString(EXTRA_TRANSITION_BITMAP_RECT); if (!TextUtils.isEmpty(bitmapRectStr)) { rect = Rect.unflattenFromString(bitmapRectStr); } if (rect == null) { rect = intentSourceBounds; } if (rect == null) { return false; } setBitmap(bitmap); setRect(rect); if (!readRectF(bundle.getFloatArray(EXTRA_TRANSITION_BITMAP_CLIPPED_RECT), mClippedRect)) { mClippedRect.set(rect); } if (!readRectF(bundle.getFloatArray(EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT), mUnclippedRect)) { mUnclippedRect.set(rect); } setAlpha(bundle.getFloat(EXTRA_TRANSITION_BITMAP_ALPHA, 1f)); setSaturation(bundle.getFloat(EXTRA_TRANSITION_BITMAP_SATURATION, 1f)); setBackground(bundle.getInt(EXTRA_TRANSITION_BITMAP_BACKGROUND, 0)); return true; } public void writeToBundle(Bundle bundle) { bundle.putParcelable(EXTRA_TRANSITION_BITMAP_URI, mUri); bundle.putString(EXTRA_TRANSITION_BITMAP_RECT, mRect.flattenToString()); if (mBitmap != null) { bundle.putBundle(EXTRA_TRANSITION_BITMAP, ActivityTransitionBitmapHelper.bitmapAsBinderBundle(mBitmap.getBitmap())); } bundle.putFloatArray(EXTRA_TRANSITION_BITMAP_CLIPPED_RECT, writeRectF(mClippedRect, new float[4])); bundle.putFloatArray(EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT, writeRectF(mUnclippedRect, new float[4])); bundle.putFloat(EXTRA_TRANSITION_BITMAP_ALPHA, mAlpha); bundle.putFloat(EXTRA_TRANSITION_BITMAP_SATURATION, mSaturation); bundle.putInt(EXTRA_TRANSITION_BITMAP_BACKGROUND, mBackground); } public void writeToIntent(Intent intent) { intent.setSourceBounds(mRect); intent.putExtra(EXTRA_TRANSITION_BITMAP_URI, mUri); intent.putExtra(EXTRA_TRANSITION_BITMAP_RECT, mRect.flattenToString()); if (mBitmap != null) { intent.putExtra(EXTRA_TRANSITION_BITMAP, ActivityTransitionBitmapHelper.bitmapAsBinderBundle(mBitmap.getBitmap())); } intent.putExtra(EXTRA_TRANSITION_BITMAP_CLIPPED_RECT, writeRectF(mClippedRect, new float[4])); intent.putExtra(EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT, writeRectF(mUnclippedRect, new float[4])); intent.putExtra(EXTRA_TRANSITION_BITMAP_ALPHA, mAlpha); intent.putExtra(EXTRA_TRANSITION_BITMAP_SATURATION, mSaturation); intent.putExtra(EXTRA_TRANSITION_BITMAP_BACKGROUND, mBackground); } public static boolean readRectF(float[] values, RectF f) { if (values == null || values.length != 4) { return false; } f.set(values[0], values[1], values[2], values[3]); return true; } public static float[] writeRectF(RectF f, float[] values) { values[0] = f.left; values[1] = f.top; values[2] = f.right; values[3] = f.bottom; return values; } /** * set bounds and bitmap */ public void createFromImageView(ImageView imageView) { createFromImageView(imageView, imageView); } /** * set bounds and bitmap * * @param backgroundView background view can be larger than the image view that will * be drawn with background color */ public void createFromImageView(ImageView view, View backgroundView) { Drawable drawable = view.getDrawable(); if (drawable instanceof BitmapDrawable) { setBitmap((BitmapDrawable) drawable); } // use background View as the outside bounds and we can fill // background color in it mClippedRect.set(0, 0, backgroundView.getWidth(), backgroundView.getHeight()); WindowLocationUtil.getLocationsInWindow(backgroundView, mClippedRect); mClippedRect.round(mRect); // get image view rects WindowLocationUtil.getImageLocationsInWindow(view, mClippedRect, mUnclippedRect); } /** * set if background is transparent, set if we want to use {@link #setClippedRect(RectF)} * instead of {@link #setRect(Rect)}. Default value is true, and the value is not * serialized. User should call it before using TransitionImageAnimation. */ public void setUseClippedRectOnTransparent(boolean ignoreBackground) { mUseClippedRectOnTransparent = ignoreBackground; } /** * get if background is not transparent, set if we want to use {@link #setClippedRect(RectF)} * instead of {@link #setRect(Rect)} */ public boolean getUseClippedRectOnTransparent() { return mUseClippedRectOnTransparent; } /** * Get optimized rect depending on the background color */ public void getOptimizedRect(Rect rect) { if (mUseClippedRectOnTransparent && mBackground == Color.TRANSPARENT) { mClippedRect.round(rect); } else { rect.set(mRect); } } @Override public String toString() { return "{TransitionImage Uri=" + mUri + " rect=" + mRect + " unclipRect=" + mUnclippedRect + " clipRect=" + mClippedRect + " bitmap=" + mBitmap + " alpha=" + mAlpha + " saturation=" + mSaturation + " background=" + mBackground; } }