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 
TransitionImage()86     public TransitionImage() {
87     }
88 
getUri()89     public Uri getUri() {
90         return mUri;
91     }
92 
setUri(Uri uri)93     public void setUri(Uri uri) {
94         mUri = uri;
95     }
96 
getBitmap()97     public BitmapDrawable getBitmap() {
98         return mBitmap;
99     }
100 
setBitmap(BitmapDrawable bitmap)101     public void setBitmap(BitmapDrawable bitmap) {
102         mBitmap = bitmap;
103     }
104 
getRect()105     public Rect getRect() {
106         return mRect;
107     }
108 
setRect(Rect rect)109     public void setRect(Rect rect) {
110         mRect.set(rect);
111     }
112 
getBackground()113     public int getBackground() {
114         return mBackground;
115     }
116 
setBackground(int color)117     public void setBackground(int color) {
118         mBackground = color;
119     }
120 
getAlpha()121     public float getAlpha() {
122         return mAlpha;
123     }
124 
setAlpha(float alpha)125     public void setAlpha(float alpha) {
126         mAlpha = alpha;
127     }
128 
getSaturation()129     public float getSaturation() {
130         return mSaturation;
131     }
132 
setSaturation(float saturation)133     public void setSaturation(float saturation) {
134         mSaturation = saturation;
135     }
136 
getUnclippedRect()137     public RectF getUnclippedRect() {
138         return mUnclippedRect;
139     }
140 
setUnclippedRect(RectF rect)141     public void setUnclippedRect(RectF rect) {
142         mUnclippedRect.set(rect);
143     }
144 
getClippedRect()145     public RectF getClippedRect() {
146         return mClippedRect;
147     }
148 
setClippedRect(RectF rect)149     public void setClippedRect(RectF rect) {
150         mClippedRect.set(rect);
151     }
152 
readMultipleFromIntent(Context context, Intent intent)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 
writeMultipleToIntent(List<TransitionImage> transitions, Intent intent)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 
readFromBundle(Context context, Rect intentSourceBounds, Bundle bundle)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 
writeToBundle(Bundle bundle)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 
writeToIntent(Intent intent)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 
readRectF(float[] values, RectF f)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 
writeRectF(RectF f, float[] values)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      */
createFromImageView(ImageView imageView)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      */
createFromImageView(ImageView view, View backgroundView)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      */
setUseClippedRectOnTransparent(boolean ignoreBackground)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      */
getUseClippedRectOnTransparent()340     public boolean getUseClippedRectOnTransparent() {
341         return mUseClippedRectOnTransparent;
342     }
343 
344     /**
345      * Get optimized rect depending on the background color
346      */
getOptimizedRect(Rect rect)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 
setUserObject(Object object)355     public void setUserObject(Object object) {
356         mUserObject = object;
357     }
358 
getUserObject()359     public Object getUserObject() {
360         return mUserObject;
361     }
362 
363     @Override
toString()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