1 /*
2  * Copyright (C) 2012 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.incallui;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.graphics.Canvas;
23 import android.graphics.drawable.BitmapDrawable;
24 import android.graphics.drawable.Drawable;
25 import android.graphics.drawable.LayerDrawable;
26 import android.view.ViewPropertyAnimator;
27 import android.widget.ImageView;
28 
29 /**
30  * Utilities for Animation.
31  */
32 public class InCallAnimationUtils {
33     private static final String LOG_TAG = InCallAnimationUtils.class.getSimpleName();
34     /**
35      * Turn on when you're interested in fading animation. Intentionally untied from other debug
36      * settings.
37      */
38     private static final boolean FADE_DBG = false;
39 
40     /**
41      * Duration for animations in msec, which can be used with
42      * {@link ViewPropertyAnimator#setDuration(long)} for example.
43      */
44     public static final int ANIMATION_DURATION = 250;
45 
InCallAnimationUtils()46     private InCallAnimationUtils() {
47     }
48 
49     /**
50      * Drawable achieving cross-fade, just like TransitionDrawable. We can have
51      * call-backs via animator object (see also {@link CrossFadeDrawable#getAnimator()}).
52      */
53     private static class CrossFadeDrawable extends LayerDrawable {
54         private final ObjectAnimator mAnimator;
55 
CrossFadeDrawable(Drawable[] layers)56         public CrossFadeDrawable(Drawable[] layers) {
57             super(layers);
58             mAnimator = ObjectAnimator.ofInt(this, "crossFadeAlpha", 0xff, 0);
59         }
60 
61         private int mCrossFadeAlpha;
62 
63         /**
64          * This will be used from ObjectAnimator.
65          * Note: this method is protected by proguard.flags so that it won't be removed
66          * automatically.
67          */
68         @SuppressWarnings("unused")
setCrossFadeAlpha(int alpha)69         public void setCrossFadeAlpha(int alpha) {
70             mCrossFadeAlpha = alpha;
71             invalidateSelf();
72         }
73 
getAnimator()74         public ObjectAnimator getAnimator() {
75             return mAnimator;
76         }
77 
78         @Override
draw(Canvas canvas)79         public void draw(Canvas canvas) {
80             Drawable first = getDrawable(0);
81             Drawable second = getDrawable(1);
82 
83             if (mCrossFadeAlpha > 0) {
84                 first.setAlpha(mCrossFadeAlpha);
85                 first.draw(canvas);
86                 first.setAlpha(255);
87             }
88 
89             if (mCrossFadeAlpha < 0xff) {
90                 second.setAlpha(0xff - mCrossFadeAlpha);
91                 second.draw(canvas);
92                 second.setAlpha(0xff);
93             }
94         }
95     }
96 
newCrossFadeDrawable(Drawable first, Drawable second)97     private static CrossFadeDrawable newCrossFadeDrawable(Drawable first, Drawable second) {
98         Drawable[] layers = new Drawable[2];
99         layers[0] = first;
100         layers[1] = second;
101         return new CrossFadeDrawable(layers);
102     }
103 
104     /**
105      * Starts cross-fade animation using TransitionDrawable. Nothing will happen if "from" and "to"
106      * are the same.
107      */
startCrossFade( final ImageView imageView, final Drawable from, final Drawable to)108     public static void startCrossFade(
109             final ImageView imageView, final Drawable from, final Drawable to) {
110         // We skip the cross-fade when those two Drawables are equal, or they are BitmapDrawables
111         // pointing to the same Bitmap.
112         final boolean drawableIsEqual = (from != null && to != null && from.equals(to));
113         final boolean hasFromImage = ((from instanceof BitmapDrawable) &&
114                 ((BitmapDrawable) from).getBitmap() != null);
115         final boolean hasToImage = ((to instanceof BitmapDrawable) &&
116                 ((BitmapDrawable) to).getBitmap() != null);
117         final boolean areSameImage = drawableIsEqual || (hasFromImage && hasToImage &&
118                 ((BitmapDrawable) from).getBitmap().equals(((BitmapDrawable) to).getBitmap()));
119 
120         if (!areSameImage) {
121             if (FADE_DBG) {
122                 log("Start cross-fade animation for " + imageView
123                         + "(" + Integer.toHexString(from.hashCode()) + " -> "
124                         + Integer.toHexString(to.hashCode()) + ")");
125             }
126 
127             CrossFadeDrawable crossFadeDrawable = newCrossFadeDrawable(from, to);
128             ObjectAnimator animator = crossFadeDrawable.getAnimator();
129             imageView.setImageDrawable(crossFadeDrawable);
130             animator.setDuration(ANIMATION_DURATION);
131             animator.addListener(new AnimatorListenerAdapter() {
132                 @Override
133                 public void onAnimationStart(Animator animation) {
134                     if (FADE_DBG) {
135                         log("cross-fade animation start ("
136                                 + Integer.toHexString(from.hashCode()) + " -> "
137                                 + Integer.toHexString(to.hashCode()) + ")");
138                     }
139                 }
140 
141                 @Override
142                 public void onAnimationEnd(Animator animation) {
143                     if (FADE_DBG) {
144                         log("cross-fade animation ended ("
145                                 + Integer.toHexString(from.hashCode()) + " -> "
146                                 + Integer.toHexString(to.hashCode()) + ")");
147                     }
148                     animation.removeAllListeners();
149                     // Workaround for issue 6300562; this will force the drawable to the
150                     // resultant one regardless of animation glitch.
151                     imageView.setImageDrawable(to);
152                 }
153             });
154             animator.start();
155 
156             /* We could use TransitionDrawable here, but it may cause some weird animation in
157              * some corner cases. See issue 6300562
158              * TODO: decide which to be used in the long run. TransitionDrawable is old but system
159              * one. Ours uses new animation framework and thus have callback (great for testing),
160              * while no framework support for the exact class.
161 
162             Drawable[] layers = new Drawable[2];
163             layers[0] = from;
164             layers[1] = to;
165             TransitionDrawable transitionDrawable = new TransitionDrawable(layers);
166             imageView.setImageDrawable(transitionDrawable);
167             transitionDrawable.startTransition(ANIMATION_DURATION); */
168             imageView.setTag(to);
169         } else if (!hasFromImage && hasToImage) {
170             imageView.setImageDrawable(to);
171             imageView.setTag(to);
172         } else {
173             if (FADE_DBG) {
174                 log("*Not* start cross-fade. " + imageView);
175             }
176         }
177     }
178 
179     // Debugging / testing code
180 
log(String msg)181     private static void log(String msg) {
182         Log.d(LOG_TAG, msg);
183     }
184 }