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