Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tv.settings.util;
     18 
     19 import com.android.tv.settings.widget.BitmapWorkerOptions;
     20 import com.android.tv.settings.widget.DrawableDownloader;
     21 
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.graphics.Color;
     25 import android.graphics.Rect;
     26 import android.graphics.RectF;
     27 import android.graphics.drawable.BitmapDrawable;
     28 import android.graphics.drawable.Drawable;
     29 import android.net.Uri;
     30 import android.os.Bundle;
     31 import android.os.Parcelable;
     32 import android.text.TextUtils;
     33 import android.view.View;
     34 import android.widget.ImageView;
     35 
     36 import java.util.ArrayList;
     37 import java.util.List;
     38 
     39 /**
     40  * Initiator calls {@link #createFromImageView(ImageView)} and
     41  * {@link #writeMultipleToIntent(List, Intent)}.
     42  * <p>
     43  * Receiver calls {@link #readMultipleFromIntent(Context, Intent)} to read source and
     44  * {@link #createFromImageView(ImageView)} for target image view;  then start animation
     45  * using {@link TransitionImageView} between these two.<p>
     46  * The matching of Uri is up to receiver, typically using {@link TransitionImageMatcher}
     47  * <p>
     48  * The transition image has three bounds, all relative to window<p>
     49  * - {@link #setRect(Rect)}  bounds of the image view, including background color<p>
     50  * - {@link #setUnclippedRect(RectF)} bounds of original bitmap without clipping,  the rect
     51  *   might be bigger than the image view<p>
     52  * - {@link #setClippedRect(RectF)} bounds of clipping<p>
     53  */
     54 public class TransitionImage {
     55 
     56     private Uri mUri;
     57     private BitmapDrawable mBitmap;
     58     private final Rect mRect = new Rect();
     59     private int mBackground = Color.TRANSPARENT;
     60     private float mAlpha = 1f;
     61     private float mSaturation = 1f;
     62     private RectF mUnclippedRect = new RectF();
     63     private RectF mClippedRect = new RectF();
     64     private Object mUserObject;
     65     private boolean mUseClippedRectOnTransparent = true;
     66 
     67     public static final String EXTRA_TRANSITION_BITMAP =
     68             "com.android.tv.settings.transition_bitmap";
     69     public static final String EXTRA_TRANSITION_BITMAP_RECT =
     70             "com.android.tv.settings.transition_bmp_rect";
     71     public static final String EXTRA_TRANSITION_BITMAP_URI =
     72             "com.android.tv.settings.transition_bmp_uri";
     73     public static final String EXTRA_TRANSITION_BITMAP_ALPHA =
     74             "com.android.tv.settings.transition_bmp_alpha";
     75     public static final String EXTRA_TRANSITION_BITMAP_SATURATION =
     76             "com.android.tv.settings.transition_bmp_saturation";
     77     public static final String EXTRA_TRANSITION_BITMAP_BACKGROUND =
     78             "com.android.tv.settings.transition_bmp_background";
     79     public static final String EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT =
     80             "com.android.tv.settings.transition_bmp_unclipped_rect";
     81     public static final String EXTRA_TRANSITION_BITMAP_CLIPPED_RECT =
     82             "com.android.tv.settings.transition_bmp_clipped_rect";
     83     public static final String EXTRA_TRANSITION_MULTIPLE_BITMAP =
     84             "com.android.tv.settings.transition_multiple_bitmap";
     85 
     86     public TransitionImage() {
     87     }
     88 
     89     public Uri getUri() {
     90         return mUri;
     91     }
     92 
     93     public void setUri(Uri uri) {
     94         mUri = uri;
     95     }
     96 
     97     public BitmapDrawable getBitmap() {
     98         return mBitmap;
     99     }
    100 
    101     public void setBitmap(BitmapDrawable bitmap) {
    102         mBitmap = bitmap;
    103     }
    104 
    105     public Rect getRect() {
    106         return mRect;
    107     }
    108 
    109     public void setRect(Rect rect) {
    110         mRect.set(rect);
    111     }
    112 
    113     public int getBackground() {
    114         return mBackground;
    115     }
    116 
    117     public void setBackground(int color) {
    118         mBackground = color;
    119     }
    120 
    121     public float getAlpha() {
    122         return mAlpha;
    123     }
    124 
    125     public void setAlpha(float alpha) {
    126         mAlpha = alpha;
    127     }
    128 
    129     public float getSaturation() {
    130         return mSaturation;
    131     }
    132 
    133     public void setSaturation(float saturation) {
    134         mSaturation = saturation;
    135     }
    136 
    137     public RectF getUnclippedRect() {
    138         return mUnclippedRect;
    139     }
    140 
    141     public void setUnclippedRect(RectF rect) {
    142         mUnclippedRect.set(rect);
    143     }
    144 
    145     public RectF getClippedRect() {
    146         return mClippedRect;
    147     }
    148 
    149     public void setClippedRect(RectF rect) {
    150         mClippedRect.set(rect);
    151     }
    152 
    153     public static List<TransitionImage> readMultipleFromIntent(Context context, Intent intent) {
    154         ArrayList<TransitionImage> transitions = new ArrayList<TransitionImage>();
    155         Bundle extras = intent.getExtras();
    156         if (extras == null) {
    157             return transitions;
    158         }
    159         TransitionImage image = new TransitionImage();
    160         if (image.readFromBundle(context, intent.getSourceBounds(), extras)) {
    161             transitions.add(image);
    162         }
    163         Parcelable[] multiple =
    164                 intent.getParcelableArrayExtra(EXTRA_TRANSITION_MULTIPLE_BITMAP);
    165         if (multiple != null) {
    166             for (int i = 0, size = multiple.length; i < size; i++) {
    167                 if (!(multiple[i] instanceof Bundle)) {
    168                     break;
    169                 }
    170                 image = new TransitionImage();
    171                 if (image.readFromBundle(context, null, (Bundle) multiple[i])) {
    172                     transitions.add(image);
    173                 }
    174             }
    175         }
    176         return transitions;
    177     }
    178 
    179     public static void writeMultipleToIntent(List<TransitionImage> transitions, Intent intent) {
    180         if (transitions == null || transitions.size() == 0) {
    181             return;
    182         }
    183         int size = transitions.size();
    184         if (size == 1) {
    185             TransitionImage image = transitions.get(0);
    186             image.writeToIntent(intent);
    187             return;
    188         }
    189         Parcelable[] multipleBundle = new Parcelable[size];
    190         for (int i = 0; i < size; i++) {
    191             Bundle b = new Bundle();
    192             transitions.get(i).writeToBundle(b);
    193             multipleBundle[i] = b;
    194         }
    195         intent.putExtra(EXTRA_TRANSITION_MULTIPLE_BITMAP, multipleBundle);
    196     }
    197 
    198     public boolean readFromBundle(Context context, Rect intentSourceBounds, Bundle bundle) {
    199         setBitmap(null);
    200         if (bundle == null) {
    201             return false;
    202         }
    203         mUri = bundle.getParcelable(EXTRA_TRANSITION_BITMAP_URI);
    204         BitmapDrawable bitmap = null;
    205         if (mUri != null) {
    206             DrawableDownloader downloader = DrawableDownloader.getInstance(context);
    207             BitmapWorkerOptions key = new BitmapWorkerOptions.Builder(context)
    208                     .resource(mUri).build();
    209             Drawable d = downloader.getLargestBitmapFromMemCache(key);
    210             if (d instanceof BitmapDrawable) {
    211                 bitmap = ((BitmapDrawable) d);
    212             }
    213         }
    214         if (bitmap == null) {
    215             if (bundle.containsKey(EXTRA_TRANSITION_BITMAP)) {
    216                 bitmap = new BitmapDrawable(context.getResources(),
    217                         ActivityTransitionBitmapHelper.getBitmapFromBinderBundle(
    218                         bundle.getBundle(EXTRA_TRANSITION_BITMAP)));
    219             }
    220             if (bitmap == null) {
    221                 return false;
    222             }
    223         }
    224         Rect rect = null;
    225         String bitmapRectStr = bundle.getString(EXTRA_TRANSITION_BITMAP_RECT);
    226         if (!TextUtils.isEmpty(bitmapRectStr)) {
    227             rect = Rect.unflattenFromString(bitmapRectStr);
    228         }
    229         if (rect == null) {
    230             rect = intentSourceBounds;
    231         }
    232         if (rect == null) {
    233             return false;
    234         }
    235         setBitmap(bitmap);
    236         setRect(rect);
    237         if (!readRectF(bundle.getFloatArray(EXTRA_TRANSITION_BITMAP_CLIPPED_RECT),
    238                 mClippedRect)) {
    239             mClippedRect.set(rect);
    240         }
    241         if (!readRectF(bundle.getFloatArray(EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT),
    242                 mUnclippedRect)) {
    243             mUnclippedRect.set(rect);
    244         }
    245         setAlpha(bundle.getFloat(EXTRA_TRANSITION_BITMAP_ALPHA, 1f));
    246         setSaturation(bundle.getFloat(EXTRA_TRANSITION_BITMAP_SATURATION, 1f));
    247         setBackground(bundle.getInt(EXTRA_TRANSITION_BITMAP_BACKGROUND, 0));
    248         return true;
    249     }
    250 
    251     public void writeToBundle(Bundle bundle) {
    252         bundle.putParcelable(EXTRA_TRANSITION_BITMAP_URI, mUri);
    253         bundle.putString(EXTRA_TRANSITION_BITMAP_RECT, mRect.flattenToString());
    254         if (mBitmap != null) {
    255             bundle.putBundle(EXTRA_TRANSITION_BITMAP,
    256                     ActivityTransitionBitmapHelper.bitmapAsBinderBundle(mBitmap.getBitmap()));
    257         }
    258         bundle.putFloatArray(EXTRA_TRANSITION_BITMAP_CLIPPED_RECT,
    259                 writeRectF(mClippedRect, new float[4]));
    260         bundle.putFloatArray(EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT,
    261                 writeRectF(mUnclippedRect, new float[4]));
    262         bundle.putFloat(EXTRA_TRANSITION_BITMAP_ALPHA, mAlpha);
    263         bundle.putFloat(EXTRA_TRANSITION_BITMAP_SATURATION, mSaturation);
    264         bundle.putInt(EXTRA_TRANSITION_BITMAP_BACKGROUND, mBackground);
    265     }
    266 
    267     public void writeToIntent(Intent intent) {
    268         intent.setSourceBounds(mRect);
    269         intent.putExtra(EXTRA_TRANSITION_BITMAP_URI, mUri);
    270         intent.putExtra(EXTRA_TRANSITION_BITMAP_RECT, mRect.flattenToString());
    271         if (mBitmap != null) {
    272             intent.putExtra(EXTRA_TRANSITION_BITMAP,
    273                     ActivityTransitionBitmapHelper.bitmapAsBinderBundle(mBitmap.getBitmap()));
    274         }
    275         intent.putExtra(EXTRA_TRANSITION_BITMAP_CLIPPED_RECT,
    276                 writeRectF(mClippedRect, new float[4]));
    277         intent.putExtra(EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT,
    278                 writeRectF(mUnclippedRect, new float[4]));
    279         intent.putExtra(EXTRA_TRANSITION_BITMAP_ALPHA, mAlpha);
    280         intent.putExtra(EXTRA_TRANSITION_BITMAP_SATURATION, mSaturation);
    281         intent.putExtra(EXTRA_TRANSITION_BITMAP_BACKGROUND, mBackground);
    282     }
    283 
    284     public static boolean readRectF(float[] values, RectF f) {
    285         if (values == null || values.length != 4) {
    286             return false;
    287         }
    288         f.set(values[0], values[1], values[2], values[3]);
    289         return true;
    290     }
    291 
    292     public static float[] writeRectF(RectF f, float[] values) {
    293         values[0] = f.left;
    294         values[1] = f.top;
    295         values[2] = f.right;
    296         values[3] = f.bottom;
    297         return values;
    298     }
    299 
    300     /**
    301      * set bounds and bitmap
    302      */
    303     public void createFromImageView(ImageView imageView) {
    304         createFromImageView(imageView, imageView);
    305     }
    306 
    307     /**
    308      * set bounds and bitmap
    309      *
    310      * @param backgroundView background view can be larger than the image view that will
    311      * be drawn with background color
    312      */
    313     public void createFromImageView(ImageView view, View backgroundView) {
    314         Drawable drawable = view.getDrawable();
    315         if (drawable instanceof BitmapDrawable) {
    316             setBitmap((BitmapDrawable) drawable);
    317         }
    318         // use background View as the outside bounds and we can fill
    319         // background color in it
    320         mClippedRect.set(0, 0, backgroundView.getWidth(), backgroundView.getHeight());
    321         WindowLocationUtil.getLocationsInWindow(backgroundView, mClippedRect);
    322         mClippedRect.round(mRect);
    323         // get image view rects
    324         WindowLocationUtil.getImageLocationsInWindow(view, mClippedRect, mUnclippedRect);
    325     }
    326 
    327     /**
    328      * set if background is transparent, set if we want to use {@link #setClippedRect(RectF)}
    329      * instead of {@link #setRect(Rect)}.  Default value is true,  and the value is not
    330      * serialized.  User should call it before using TransitionImageAnimation.
    331      */
    332     public void setUseClippedRectOnTransparent(boolean ignoreBackground) {
    333         mUseClippedRectOnTransparent = ignoreBackground;
    334     }
    335 
    336     /**
    337      * get if background is not transparent, set if we want to use {@link #setClippedRect(RectF)}
    338      * instead of {@link #setRect(Rect)}
    339      */
    340     public boolean getUseClippedRectOnTransparent() {
    341         return mUseClippedRectOnTransparent;
    342     }
    343 
    344     /**
    345      * Get optimized rect depending on the background color
    346      */
    347     public void getOptimizedRect(Rect rect) {
    348         if (mUseClippedRectOnTransparent && mBackground == Color.TRANSPARENT) {
    349             mClippedRect.round(rect);
    350         } else {
    351             rect.set(mRect);
    352         }
    353     }
    354 
    355     public void setUserObject(Object object) {
    356         mUserObject = object;
    357     }
    358 
    359     public Object getUserObject() {
    360         return mUserObject;
    361     }
    362 
    363     @Override
    364     public String toString() {
    365         return "{TransitionImage Uri=" + mUri + " rect=" + mRect
    366                 + " unclipRect=" + mUnclippedRect + " clipRect=" + mClippedRect
    367                 + " bitmap=" + mBitmap + " alpha=" + mAlpha + " saturation=" + mSaturation
    368                 + " background=" + mBackground;
    369     }
    370 }
    371