Contact detail transition animation

Change-Id: I0ee47a78940d68092c518a35fbe1c78a2b5323db
diff --git a/src/com/android/contacts/views/detail/ContactDetailFragment.java b/src/com/android/contacts/views/detail/ContactDetailFragment.java
index 91641ea..de00eff 100644
--- a/src/com/android/contacts/views/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/views/detail/ContactDetailFragment.java
@@ -35,6 +35,7 @@
 import com.android.contacts.views.ContactLoader;
 import com.android.contacts.views.GroupMetaData;
 import com.android.contacts.views.editor.SelectAccountDialogFragment;
+import com.android.contacts.widget.TransitionAnimationView;
 import com.android.internal.telephony.ITelephony;
 
 import android.accounts.Account;
@@ -98,7 +99,6 @@
 import android.widget.BaseAdapter;
 import android.widget.Button;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.Toast;
@@ -133,6 +133,7 @@
     private final ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
     private int mNumPhoneNumbers = 0;
     private String mDefaultCountryIso;
+    private boolean mContactDataDisplayed;
 
     /**
      * Device capability: Set during buildEntries and used in the long-press context menu
@@ -172,6 +173,8 @@
     private ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
     private LayoutInflater mInflater;
 
+    private boolean mTransitionAnimationRequested;
+
     public ContactDetailFragment() {
         // Explicit constructor for inflation
 
@@ -264,6 +267,8 @@
         }
 
         mLookupUri = lookupUri;
+        mTransitionAnimationRequested = mContactDataDisplayed;
+        mContactDataDisplayed = true;
         if (mLookupUri == null) {
             mContactData = null;
             bindData();
@@ -283,6 +288,11 @@
             getActivity().invalidateOptionsMenu();
         }
 
+        if (mTransitionAnimationRequested) {
+            TransitionAnimationView.startAnimation(mView, mContactData == null);
+            mTransitionAnimationRequested = false;
+        }
+
         if (mContactData == null) {
             mView.setVisibility(View.INVISIBLE);
             return;
@@ -323,8 +333,6 @@
         }
 
         mView.setVisibility(View.VISIBLE);
-
-        getActivity().invalidateOptionsMenu();
     }
 
     /**
diff --git a/src/com/android/contacts/widget/TransitionAnimationView.java b/src/com/android/contacts/widget/TransitionAnimationView.java
new file mode 100644
index 0000000..938ff8a
--- /dev/null
+++ b/src/com/android/contacts/widget/TransitionAnimationView.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2010 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.contacts.widget;
+
+import com.android.contacts.R;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorInflater;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
+
+/**
+ * A container for a view that needs to have exit/enter animations when rebinding data.
+ * This layout should have a single child.  Just before rebinding data that child
+ * should make this call:
+ * <pre>
+ *   TransitionAnimationView.startAnimation(this);
+ * </pre>
+ */
+public class TransitionAnimationView extends FrameLayout implements AnimatorListener {
+
+    private View mPreviousStateView;
+    private Bitmap mPreviousStateBitmap;
+    private int mEnterAnimationId;
+    private int mExitAnimationId;
+    private int mAnimationDuration;
+    private Rect mClipMargins = new Rect();
+    private Rect mClipRect = new Rect();
+    private Animator mEnterAnimation;
+    private Animator mExitAnimation;
+
+    public TransitionAnimationView(Context context) {
+        this(context, null, 0);
+    }
+
+    public TransitionAnimationView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TransitionAnimationView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = getContext().obtainStyledAttributes(
+                attrs, R.styleable.TransitionAnimationView);
+
+        mEnterAnimationId = a.getResourceId(R.styleable.TransitionAnimationView_enterAnimation,
+                android.R.anim.animator_fade_in);
+        mExitAnimationId = a.getResourceId(R.styleable.TransitionAnimationView_exitAnimation,
+                android.R.anim.animator_fade_out);
+        mClipMargins.left = a.getDimensionPixelOffset(
+                R.styleable.TransitionAnimationView_clipMarginLeft, 0);
+        mClipMargins.top = a.getDimensionPixelOffset(
+                R.styleable.TransitionAnimationView_clipMarginTop, 0);
+        mClipMargins.right = a.getDimensionPixelOffset(
+                R.styleable.TransitionAnimationView_clipMarginRight, 0);
+        mClipMargins.bottom = a.getDimensionPixelOffset(
+                R.styleable.TransitionAnimationView_clipMarginBottom, 0);
+        mAnimationDuration = a.getInt(
+                R.styleable.TransitionAnimationView_animationDuration, 100);
+
+        a.recycle();
+
+        mPreviousStateView = new View(context);
+        mPreviousStateView.setVisibility(View.INVISIBLE);
+        addView(mPreviousStateView);
+
+        mEnterAnimation = AnimatorInflater.loadAnimator(getContext(), mEnterAnimationId);
+        if (mEnterAnimation == null) {
+            throw new IllegalArgumentException("Invalid enter animation: " + mEnterAnimationId);
+        }
+        mEnterAnimation.addListener(this);
+
+        mExitAnimation = AnimatorInflater.loadAnimator(getContext(), mExitAnimationId);
+        if (mExitAnimation == null) {
+            throw new IllegalArgumentException("Invalid exit animation: " + mExitAnimationId);
+        }
+
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (changed) {
+            mPreviousStateBitmap = Bitmap.createBitmap(
+                    right - left, bottom - top, Bitmap.Config.ARGB_8888);
+            mPreviousStateView.setBackgroundDrawable(
+                    new BitmapDrawable(getContext().getResources(), mPreviousStateBitmap));
+            mClipRect.set(mClipMargins.left, mClipMargins.top,
+                    right - left - mClipMargins.right, bottom - top - mClipMargins.bottom);
+        }
+    }
+
+    public static void startAnimation(View view, boolean closing) {
+        TransitionAnimationView container = null;
+        ViewParent parent = view.getParent();
+        while (parent instanceof View) {
+            if (parent instanceof TransitionAnimationView) {
+                container = (TransitionAnimationView) parent;
+                break;
+            }
+            parent = parent.getParent();
+        }
+
+        if (container != null) {
+            container.start(view, closing);
+        }
+    }
+
+    private void start(View view, boolean closing) {
+        if (view.getVisibility() != View.VISIBLE) {
+            if (!closing) {
+                mEnterAnimation.setTarget(view);
+                mEnterAnimation.start();
+            }
+        } else if (closing) {
+            mExitAnimation.setTarget(view);
+            mExitAnimation.start();
+        } else {
+            Canvas canvas = new Canvas(mPreviousStateBitmap);
+            Paint paint = new Paint();
+            paint.setColor(Color.TRANSPARENT);
+            canvas.drawRect(0, 0, mPreviousStateBitmap.getWidth(), mPreviousStateBitmap.getHeight(),
+                    paint);
+            canvas.clipRect(mClipRect);
+            view.draw(canvas);
+            mPreviousStateView.setVisibility(View.VISIBLE);
+
+            mEnterAnimation.setTarget(view);
+            mEnterAnimation.setDuration(mAnimationDuration);
+            mEnterAnimation.start();
+        }
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+        mPreviousStateView.setVisibility(View.INVISIBLE);
+    }
+
+    @Override
+    public void onAnimationEnd(Animator animation) {
+        mPreviousStateView.setVisibility(View.INVISIBLE);
+    }
+
+    @Override
+    public void onAnimationStart(Animator animation) {
+    }
+
+    @Override
+    public void onAnimationRepeat(Animator animation) {
+    }
+}