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
17package com.android.incallui;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.graphics.Canvas;
23import android.graphics.drawable.BitmapDrawable;
24import android.graphics.drawable.Drawable;
25import android.graphics.drawable.LayerDrawable;
26import android.view.ViewPropertyAnimator;
27import android.widget.ImageView;
28
29/**
30 * Utilities for Animation.
31 */
32public 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
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
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")
69        public void setCrossFadeAlpha(int alpha) {
70            mCrossFadeAlpha = alpha;
71            invalidateSelf();
72        }
73
74        public ObjectAnimator getAnimator() {
75            return mAnimator;
76        }
77
78        @Override
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
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     */
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
181    private static void log(String msg) {
182        Log.d(LOG_TAG, msg);
183    }
184}