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 android.content.Context;
     20 import android.content.Intent;
     21 import android.graphics.Color;
     22 import android.graphics.Rect;
     23 import android.graphics.RectF;
     24 import android.graphics.drawable.BitmapDrawable;
     25 import android.graphics.drawable.Drawable;
     26 import android.net.Uri;
     27 import android.os.Bundle;
     28 import android.os.Parcelable;
     29 import android.text.TextUtils;
     30 import android.view.View;
     31 import android.widget.ImageView;
     32 
     33 import com.android.tv.settings.widget.BitmapWorkerOptions;
     34 import com.android.tv.settings.widget.DrawableDownloader;
     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 final RectF mUnclippedRect = new RectF();
     63     private final RectF mClippedRect = new RectF();
     64     private boolean mUseClippedRectOnTransparent = true;
     65 
     66     public static final String EXTRA_TRANSITION_BITMAP =
     67             "com.android.tv.settings.transition_bitmap";
     68     public static final String EXTRA_TRANSITION_BITMAP_RECT =
     69             "com.android.tv.settings.transition_bmp_rect";
     70     public static final String EXTRA_TRANSITION_BITMAP_URI =
     71             "com.android.tv.settings.transition_bmp_uri";
     72     public static final String EXTRA_TRANSITION_BITMAP_ALPHA =
     73             "com.android.tv.settings.transition_bmp_alpha";
     74     public static final String EXTRA_TRANSITION_BITMAP_SATURATION =
     75             "com.android.tv.settings.transition_bmp_saturation";
     76     public static final String EXTRA_TRANSITION_BITMAP_BACKGROUND =
     77             "com.android.tv.settings.transition_bmp_background";
     78     public static final String EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT =
     79             "com.android.tv.settings.transition_bmp_unclipped_rect";
     80     public static final String EXTRA_TRANSITION_BITMAP_CLIPPED_RECT =
     81             "com.android.tv.settings.transition_bmp_clipped_rect";
     82     public static final String EXTRA_TRANSITION_MULTIPLE_BITMAP =
     83             "com.android.tv.settings.transition_multiple_bitmap";
     84 
     85     public TransitionImage() {
     86     }
     87 
     88     public Uri getUri() {
     89         return mUri;
     90     }
     91 
     92     public void setUri(Uri uri) {
     93         mUri = uri;
     94     }
     95 
     96     public BitmapDrawable getBitmap() {
     97         return mBitmap;
     98     }
     99 
    100     public void setBitmap(BitmapDrawable bitmap) {
    101         mBitmap = bitmap;
    102     }
    103 
    104     public Rect getRect() {
    105         return mRect;
    106     }
    107 
    108     public void setRect(Rect rect) {
    109         mRect.set(rect);
    110     }
    111 
    112     public int getBackground() {
    113         return mBackground;
    114     }
    115 
    116     public void setBackground(int color) {
    117         mBackground = color;
    118     }
    119 
    120     public float getAlpha() {
    121         return mAlpha;
    122     }
    123 
    124     public void setAlpha(float alpha) {
    125         mAlpha = alpha;
    126     }
    127 
    128     public float getSaturation() {
    129         return mSaturation;
    130     }
    131 
    132     public void setSaturation(float saturation) {
    133         mSaturation = saturation;
    134     }
    135 
    136     public RectF getUnclippedRect() {
    137         return mUnclippedRect;
    138     }
    139 
    140     public void setUnclippedRect(RectF rect) {
    141         mUnclippedRect.set(rect);
    142     }
    143 
    144     public RectF getClippedRect() {
    145         return mClippedRect;
    146     }
    147 
    148     public void setClippedRect(RectF rect) {
    149         mClippedRect.set(rect);
    150     }
    151 
    152     public static List<TransitionImage> readMultipleFromIntent(Context context, Intent intent) {
    153         ArrayList<TransitionImage> transitions = new ArrayList<>();
    154         Bundle extras = intent.getExtras();
    155         if (extras == null) {
    156             return transitions;
    157         }
    158         TransitionImage image = new TransitionImage();
    159         if (image.readFromBundle(context, intent.getSourceBounds(), extras)) {
    160             transitions.add(image);
    161         }
    162         Parcelable[] multiple =
    163                 intent.getParcelableArrayExtra(EXTRA_TRANSITION_MULTIPLE_BITMAP);
    164         if (multiple != null) {
    165             for (int i = 0, size = multiple.length; i < size; i++) {
    166                 if (!(multiple[i] instanceof Bundle)) {
    167                     break;
    168                 }
    169                 image = new TransitionImage();
    170                 if (image.readFromBundle(context, null, (Bundle) multiple[i])) {
    171                     transitions.add(image);
    172                 }
    173             }
    174         }
    175         return transitions;
    176     }
    177 
    178     public static void writeMultipleToIntent(List<TransitionImage> transitions, Intent intent) {
    179         if (transitions == null || transitions.size() == 0) {
    180             return;
    181         }
    182         int size = transitions.size();
    183         if (size == 1) {
    184             TransitionImage image = transitions.get(0);
    185             image.writeToIntent(intent);
    186             return;
    187         }
    188         Parcelable[] multipleBundle = new Parcelable[size];
    189         for (int i = 0; i < size; i++) {
    190             Bundle b = new Bundle();
    191             transitions.get(i).writeToBundle(b);
    192             multipleBundle[i] = b;
    193         }
    194         intent.putExtra(EXTRA_TRANSITION_MULTIPLE_BITMAP, multipleBundle);
    195     }
    196 
    197     public boolean readFromBundle(Context context, Rect intentSourceBounds, Bundle bundle) {
    198         setBitmap(null);
    199         if (bundle == null) {
    200             return false;
    201         }
    202         mUri = bundle.getParcelable(EXTRA_TRANSITION_BITMAP_URI);
    203         BitmapDrawable bitmap = null;
    204         if (mUri != null) {
    205             DrawableDownloader downloader = DrawableDownloader.getInstance(context);
    206             BitmapWorkerOptions key = new BitmapWorkerOptions.Builder(context)
    207                     .resource(mUri).build();
    208             bitmap = downloader.getLargestBitmapFromMemCache(key);
    209         }
    210         if (bitmap == null) {
    211             if (bundle.containsKey(EXTRA_TRANSITION_BITMAP)) {
    212                 bitmap = new BitmapDrawable(context.getResources(),
    213                         ActivityTransitionBitmapHelper.getBitmapFromBinderBundle(
    214                         bundle.getBundle(EXTRA_TRANSITION_BITMAP)));
    215             }
    216             if (bitmap == null) {
    217                 return false;
    218             }
    219         }
    220         Rect rect = null;
    221         String bitmapRectStr = bundle.getString(EXTRA_TRANSITION_BITMAP_RECT);
    222         if (!TextUtils.isEmpty(bitmapRectStr)) {
    223             rect = Rect.unflattenFromString(bitmapRectStr);
    224         }
    225         if (rect == null) {
    226             rect = intentSourceBounds;
    227         }
    228         if (rect == null) {
    229             return false;
    230         }
    231         setBitmap(bitmap);
    232         setRect(rect);
    233         if (!readRectF(bundle.getFloatArray(EXTRA_TRANSITION_BITMAP_CLIPPED_RECT),
    234                 mClippedRect)) {
    235             mClippedRect.set(rect);
    236         }
    237         if (!readRectF(bundle.getFloatArray(EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT),
    238                 mUnclippedRect)) {
    239             mUnclippedRect.set(rect);
    240         }
    241         setAlpha(bundle.getFloat(EXTRA_TRANSITION_BITMAP_ALPHA, 1f));
    242         setSaturation(bundle.getFloat(EXTRA_TRANSITION_BITMAP_SATURATION, 1f));
    243         setBackground(bundle.getInt(EXTRA_TRANSITION_BITMAP_BACKGROUND, 0));
    244         return true;
    245     }
    246 
    247     public void writeToBundle(Bundle bundle) {
    248         bundle.putParcelable(EXTRA_TRANSITION_BITMAP_URI, mUri);
    249         bundle.putString(EXTRA_TRANSITION_BITMAP_RECT, mRect.flattenToString());
    250         if (mBitmap != null) {
    251             bundle.putBundle(EXTRA_TRANSITION_BITMAP,
    252                     ActivityTransitionBitmapHelper.bitmapAsBinderBundle(mBitmap.getBitmap()));
    253         }
    254         bundle.putFloatArray(EXTRA_TRANSITION_BITMAP_CLIPPED_RECT,
    255                 writeRectF(mClippedRect, new float[4]));
    256         bundle.putFloatArray(EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT,
    257                 writeRectF(mUnclippedRect, new float[4]));
    258         bundle.putFloat(EXTRA_TRANSITION_BITMAP_ALPHA, mAlpha);
    259         bundle.putFloat(EXTRA_TRANSITION_BITMAP_SATURATION, mSaturation);
    260         bundle.putInt(EXTRA_TRANSITION_BITMAP_BACKGROUND, mBackground);
    261     }
    262 
    263     public void writeToIntent(Intent intent) {
    264         intent.setSourceBounds(mRect);
    265         intent.putExtra(EXTRA_TRANSITION_BITMAP_URI, mUri);
    266         intent.putExtra(EXTRA_TRANSITION_BITMAP_RECT, mRect.flattenToString());
    267         if (mBitmap != null) {
    268             intent.putExtra(EXTRA_TRANSITION_BITMAP,
    269                     ActivityTransitionBitmapHelper.bitmapAsBinderBundle(mBitmap.getBitmap()));
    270         }
    271         intent.putExtra(EXTRA_TRANSITION_BITMAP_CLIPPED_RECT,
    272                 writeRectF(mClippedRect, new float[4]));
    273         intent.putExtra(EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT,
    274                 writeRectF(mUnclippedRect, new float[4]));
    275         intent.putExtra(EXTRA_TRANSITION_BITMAP_ALPHA, mAlpha);
    276         intent.putExtra(EXTRA_TRANSITION_BITMAP_SATURATION, mSaturation);
    277         intent.putExtra(EXTRA_TRANSITION_BITMAP_BACKGROUND, mBackground);
    278     }
    279 
    280     public static boolean readRectF(float[] values, RectF f) {
    281         if (values == null || values.length != 4) {
    282             return false;
    283         }
    284         f.set(values[0], values[1], values[2], values[3]);
    285         return true;
    286     }
    287 
    288     public static float[] writeRectF(RectF f, float[] values) {
    289         values[0] = f.left;
    290         values[1] = f.top;
    291         values[2] = f.right;
    292         values[3] = f.bottom;
    293         return values;
    294     }
    295 
    296     /**
    297      * set bounds and bitmap
    298      */
    299     public void createFromImageView(ImageView imageView) {
    300         createFromImageView(imageView, imageView);
    301     }
    302 
    303     /**
    304      * set bounds and bitmap
    305      *
    306      * @param backgroundView background view can be larger than the image view that will
    307      * be drawn with background color
    308      */
    309     public void createFromImageView(ImageView view, View backgroundView) {
    310         Drawable drawable = view.getDrawable();
    311         if (drawable instanceof BitmapDrawable) {
    312             setBitmap((BitmapDrawable) drawable);
    313         }
    314         // use background View as the outside bounds and we can fill
    315         // background color in it
    316         mClippedRect.set(0, 0, backgroundView.getWidth(), backgroundView.getHeight());
    317         WindowLocationUtil.getLocationsInWindow(backgroundView, mClippedRect);
    318         mClippedRect.round(mRect);
    319         // get image view rects
    320         WindowLocationUtil.getImageLocationsInWindow(view, mClippedRect, mUnclippedRect);
    321     }
    322 
    323     /**
    324      * set if background is transparent, set if we want to use {@link #setClippedRect(RectF)}
    325      * instead of {@link #setRect(Rect)}.  Default value is true,  and the value is not
    326      * serialized.  User should call it before using TransitionImageAnimation.
    327      */
    328     public void setUseClippedRectOnTransparent(boolean ignoreBackground) {
    329         mUseClippedRectOnTransparent = ignoreBackground;
    330     }
    331 
    332     /**
    333      * get if background is not transparent, set if we want to use {@link #setClippedRect(RectF)}
    334      * instead of {@link #setRect(Rect)}
    335      */
    336     public boolean getUseClippedRectOnTransparent() {
    337         return mUseClippedRectOnTransparent;
    338     }
    339 
    340     /**
    341      * Get optimized rect depending on the background color
    342      */
    343     public void getOptimizedRect(Rect rect) {
    344         if (mUseClippedRectOnTransparent && mBackground == Color.TRANSPARENT) {
    345             mClippedRect.round(rect);
    346         } else {
    347             rect.set(mRect);
    348         }
    349     }
    350 
    351     @Override
    352     public String toString() {
    353         return "{TransitionImage Uri=" + mUri + " rect=" + mRect
    354                 + " unclipRect=" + mUnclippedRect + " clipRect=" + mClippedRect
    355                 + " bitmap=" + mBitmap + " alpha=" + mAlpha + " saturation=" + mSaturation
    356                 + " background=" + mBackground;
    357     }
    358 }
    359