Blanket copy of PhoneApp to services/Telephony.

First phase of splitting out InCallUI from PhoneApp.

Change-Id: I237341c4ff00e96c677caa4580b251ef3432931b
diff --git a/src/com/android/phone/AnimationUtils.java b/src/com/android/phone/AnimationUtils.java
new file mode 100644
index 0000000..f7d9e2e
--- /dev/null
+++ b/src/com/android/phone/AnimationUtils.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.widget.ImageView;
+
+/**
+ * Utilities for Animation.
+ */
+public class AnimationUtils {
+    private static final String LOG_TAG = AnimationUtils.class.getSimpleName();
+    /**
+     * Turn on when you're interested in fading animation. Intentionally untied from other debug
+     * settings.
+     */
+    private static final boolean FADE_DBG = false;
+
+    /**
+     * Duration for animations in msec, which can be used with
+     * {@link ViewPropertyAnimator#setDuration(long)} for example.
+     */
+    public static final int ANIMATION_DURATION = 250;
+
+    private AnimationUtils() {
+    }
+
+    /**
+     * Simple Utility class that runs fading animations on specified views.
+     */
+    public static class Fade {
+
+        // View tag that's set during the fade-out animation; see hide() and
+        // isFadingOut().
+        private static final int FADE_STATE_KEY = R.id.fadeState;
+        private static final String FADING_OUT = "fading_out";
+
+        /**
+         * Sets the visibility of the specified view to View.VISIBLE and then
+         * fades it in. If the view is already visible (and not in the middle
+         * of a fade-out animation), this method will return without doing
+         * anything.
+         *
+         * @param view The view to be faded in
+         */
+        public static void show(final View view) {
+            if (FADE_DBG) log("Fade: SHOW view " + view + "...");
+            if (FADE_DBG) log("Fade: - visibility = " + view.getVisibility());
+            if ((view.getVisibility() != View.VISIBLE) || isFadingOut(view)) {
+                view.animate().cancel();
+                // ...and clear the FADE_STATE_KEY tag in case we just
+                // canceled an in-progress fade-out animation.
+                view.setTag(FADE_STATE_KEY, null);
+
+                view.setAlpha(0);
+                view.setVisibility(View.VISIBLE);
+                view.animate().setDuration(ANIMATION_DURATION);
+                view.animate().alpha(1);
+                if (FADE_DBG) log("Fade: ==> SHOW " + view
+                                  + " DONE.  Set visibility = " + View.VISIBLE);
+            } else {
+                if (FADE_DBG) log("Fade: ==> Ignoring, already visible AND not fading out.");
+            }
+        }
+
+        /**
+         * Fades out the specified view and then sets its visibility to the
+         * specified value (either View.INVISIBLE or View.GONE). If the view
+         * is not currently visibile, the method will return without doing
+         * anything.
+         *
+         * Note that *during* the fade-out the view itself will still have
+         * visibility View.VISIBLE, although the isFadingOut() method will
+         * return true (in case the UI code needs to detect this state.)
+         *
+         * @param view The view to be hidden
+         * @param visibility The value to which the view's visibility will be
+         *                   set after it fades out.
+         *                   Must be either View.INVISIBLE or View.GONE.
+         */
+        public static void hide(final View view, final int visibility) {
+            if (FADE_DBG) log("Fade: HIDE view " + view + "...");
+            if (view.getVisibility() == View.VISIBLE &&
+                (visibility == View.INVISIBLE || visibility == View.GONE)) {
+
+                // Use a view tag to mark this view as being in the middle
+                // of a fade-out animation.
+                view.setTag(FADE_STATE_KEY, FADING_OUT);
+
+                view.animate().cancel();
+                view.animate().setDuration(ANIMATION_DURATION);
+                view.animate().alpha(0f).setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        view.setAlpha(1);
+                        view.setVisibility(visibility);
+                        view.animate().setListener(null);
+                        // ...and we're done with the fade-out, so clear the view tag.
+                        view.setTag(FADE_STATE_KEY, null);
+                        if (FADE_DBG) log("Fade: HIDE " + view
+                                + " DONE.  Set visibility = " + visibility);
+                    }
+                });
+            }
+        }
+
+        /**
+         * @return true if the specified view is currently in the middle
+         * of a fade-out animation.  (During the fade-out, the view's
+         * visibility is still VISIBLE, although in many cases the UI
+         * should behave as if it's already invisible or gone.  This
+         * method allows the UI code to detect that state.)
+         *
+         * @see #hide(View, int)
+         */
+        public static boolean isFadingOut(final View view) {
+            if (FADE_DBG) {
+                log("Fade: isFadingOut view " + view + "...");
+                log("Fade:   - getTag() returns: " + view.getTag(FADE_STATE_KEY));
+                log("Fade:   - returning: " + (view.getTag(FADE_STATE_KEY) == FADING_OUT));
+            }
+            return (view.getTag(FADE_STATE_KEY) == FADING_OUT);
+        }
+
+    }
+
+    /**
+     * Drawable achieving cross-fade, just like TransitionDrawable. We can have
+     * call-backs via animator object (see also {@link CrossFadeDrawable#getAnimator()}).
+     */
+    private static class CrossFadeDrawable extends LayerDrawable {
+        private final ObjectAnimator mAnimator;
+
+        public CrossFadeDrawable(Drawable[] layers) {
+            super(layers);
+            mAnimator = ObjectAnimator.ofInt(this, "crossFadeAlpha", 0xff, 0);
+        }
+
+        private int mCrossFadeAlpha;
+
+        /**
+         * This will be used from ObjectAnimator.
+         * Note: this method is protected by proguard.flags so that it won't be removed
+         * automatically.
+         */
+        @SuppressWarnings("unused")
+        public void setCrossFadeAlpha(int alpha) {
+            mCrossFadeAlpha = alpha;
+            invalidateSelf();
+        }
+
+        public ObjectAnimator getAnimator() {
+            return mAnimator;
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            Drawable first = getDrawable(0);
+            Drawable second = getDrawable(1);
+
+            if (mCrossFadeAlpha > 0) {
+                first.setAlpha(mCrossFadeAlpha);
+                first.draw(canvas);
+                first.setAlpha(255);
+            }
+
+            if (mCrossFadeAlpha < 0xff) {
+                second.setAlpha(0xff - mCrossFadeAlpha);
+                second.draw(canvas);
+                second.setAlpha(0xff);
+            }
+        }
+    }
+
+    private static CrossFadeDrawable newCrossFadeDrawable(Drawable first, Drawable second) {
+        Drawable[] layers = new Drawable[2];
+        layers[0] = first;
+        layers[1] = second;
+        return new CrossFadeDrawable(layers);
+    }
+
+    /**
+     * Starts cross-fade animation using TransitionDrawable. Nothing will happen if "from" and "to"
+     * are the same.
+     */
+    public static void startCrossFade(
+            final ImageView imageView, final Drawable from, final Drawable to) {
+        // We skip the cross-fade when those two Drawables are equal, or they are BitmapDrawables
+        // pointing to the same Bitmap.
+        final boolean areSameImage = from.equals(to) ||
+                ((from instanceof BitmapDrawable)
+                        && (to instanceof BitmapDrawable)
+                        && ((BitmapDrawable) from).getBitmap()
+                                .equals(((BitmapDrawable) to).getBitmap()));
+        if (!areSameImage) {
+            if (FADE_DBG) {
+                log("Start cross-fade animation for " + imageView
+                        + "(" + Integer.toHexString(from.hashCode()) + " -> "
+                        + Integer.toHexString(to.hashCode()) + ")");
+            }
+
+            CrossFadeDrawable crossFadeDrawable = newCrossFadeDrawable(from, to);
+            ObjectAnimator animator = crossFadeDrawable.getAnimator();
+            imageView.setImageDrawable(crossFadeDrawable);
+            animator.setDuration(ANIMATION_DURATION);
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    if (FADE_DBG) {
+                        log("cross-fade animation start ("
+                                + Integer.toHexString(from.hashCode()) + " -> "
+                                + Integer.toHexString(to.hashCode()) + ")");
+                    }
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (FADE_DBG) {
+                        log("cross-fade animation ended ("
+                                + Integer.toHexString(from.hashCode()) + " -> "
+                                + Integer.toHexString(to.hashCode()) + ")");
+                    }
+                    animation.removeAllListeners();
+                    // Workaround for issue 6300562; this will force the drawable to the
+                    // resultant one regardless of animation glitch.
+                    imageView.setImageDrawable(to);
+                }
+            });
+            animator.start();
+
+            /* We could use TransitionDrawable here, but it may cause some weird animation in
+             * some corner cases. See issue 6300562
+             * TODO: decide which to be used in the long run. TransitionDrawable is old but system
+             * one. Ours uses new animation framework and thus have callback (great for testing),
+             * while no framework support for the exact class.
+
+            Drawable[] layers = new Drawable[2];
+            layers[0] = from;
+            layers[1] = to;
+            TransitionDrawable transitionDrawable = new TransitionDrawable(layers);
+            imageView.setImageDrawable(transitionDrawable);
+            transitionDrawable.startTransition(ANIMATION_DURATION); */
+            imageView.setTag(to);
+        } else {
+            if (FADE_DBG) {
+                log("*Not* start cross-fade. " + imageView);
+            }
+        }
+    }
+
+    // Debugging / testing code
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
\ No newline at end of file