Merge "Fixed various issues around cross-fades"
diff --git a/res/layout-sw580dp/contact_detail_container.xml b/res/layout-sw580dp/contact_detail_container.xml
index 6ddc98d..fc09dfb 100644
--- a/res/layout-sw580dp/contact_detail_container.xml
+++ b/res/layout-sw580dp/contact_detail_container.xml
@@ -16,10 +16,7 @@
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:paddingLeft="16dip"
-    android:paddingTop="16dip"
-    android:paddingRight="16dip">
+    android:layout_height="match_parent">
 
     <android.support.v4.view.ViewPager
         android:id="@+id/pager"
diff --git a/res/layout-sw580dp/people_activity.xml b/res/layout-sw580dp/people_activity.xml
index 80ede2d..f93e55e 100644
--- a/res/layout-sw580dp/people_activity.xml
+++ b/res/layout-sw580dp/people_activity.xml
@@ -53,18 +53,11 @@
 
         <view
             class="com.android.contacts.widget.TransitionAnimationView"
-            android:id="@+id/details_view"
+            android:id="@+id/contact_details_view"
             android:layout_width="0dip"
             android:layout_height="match_parent"
             android:layout_weight="1"
             android:background="@color/background_primary"
-            ex:clipMarginLeft="0dip"
-            ex:clipMarginTop="3dip"
-            ex:clipMarginRight="3dip"
-            ex:clipMarginBottom="9dip"
-            ex:enterAnimation="@android:animator/fade_in"
-            ex:exitAnimation="@android:animator/fade_out"
-            ex:animationDuration="200"
             android:visibility="gone">
 
             <!-- This layout includes all possible views needed for a contact detail page -->
@@ -72,7 +65,10 @@
                 android:id="@+id/contact_detail_container"
                 layout="@layout/contact_detail_container"
                 android:layout_width="match_parent"
-                android:layout_height="match_parent" />
+                android:layout_height="match_parent"
+                android:layout_marginLeft="16dip"
+                android:layout_marginTop="16dip"
+                android:layout_marginRight="16dip" />
 
             <!-- This invisible worker fragment loads the contact's details -->
             <fragment
@@ -81,6 +77,16 @@
                 android:layout_height="0dip"
                 android:layout_width="0dip"
                 android:visibility="gone"/>
+        </view>
+
+        <view
+            class="com.android.contacts.widget.TransitionAnimationView"
+            android:id="@+id/group_details_view"
+            android:layout_width="0dip"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:background="@color/background_primary"
+            android:visibility="gone">
 
             <!-- This is the group detail page -->
             <fragment
diff --git a/res/layout-sw680dp-w1000dp/people_activity.xml b/res/layout-sw680dp-w1000dp/people_activity.xml
index f45324e..2dea4eb 100644
--- a/res/layout-sw680dp-w1000dp/people_activity.xml
+++ b/res/layout-sw680dp-w1000dp/people_activity.xml
@@ -55,7 +55,7 @@
 
         <view
             class="com.android.contacts.widget.TransitionAnimationView"
-            android:id="@+id/details_view"
+            android:id="@+id/contact_details_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             ex:layout_narrowParentWidth="800dip"
@@ -64,13 +64,6 @@
             ex:layout_wideParentWidth="1280dip"
             ex:layout_wideMarginLeft="0dip"
             ex:layout_wideMarginRight="0dip"
-            ex:clipMarginLeft="0dip"
-            ex:clipMarginTop="3dip"
-            ex:clipMarginRight="3dip"
-            ex:clipMarginBottom="9dip"
-            ex:enterAnimation="@android:animator/fade_in"
-            ex:exitAnimation="@android:animator/fade_out"
-            ex:animationDuration="200"
             android:visibility="gone">
 
             <!-- This layout includes all possible views needed for a contact detail page -->
@@ -87,6 +80,20 @@
                 android:layout_height="0dip"
                 android:layout_width="0dip"
                 android:visibility="gone"/>
+        </view>
+
+        <view
+            class="com.android.contacts.widget.TransitionAnimationView"
+            android:id="@+id/group_details_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            ex:layout_narrowParentWidth="800dip"
+            ex:layout_narrowMarginLeft="0dip"
+            ex:layout_narrowMarginRight="0dip"
+            ex:layout_wideParentWidth="1280dip"
+            ex:layout_wideMarginLeft="0dip"
+            ex:layout_wideMarginRight="0dip"
+            android:visibility="gone">
 
             <!-- This is the group detail page -->
             <fragment
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index b9a534c..4402d05 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -56,16 +56,6 @@
         <attr name="ratio" format="float"/>
     </declare-styleable>
 
-    <declare-styleable name="TransitionAnimationView">
-        <attr name="clipMarginLeft" format="dimension"/>
-        <attr name="clipMarginRight" format="dimension"/>
-        <attr name="clipMarginTop" format="dimension"/>
-        <attr name="clipMarginBottom" format="dimension"/>
-        <attr name="enterAnimation" format="reference"/>
-        <attr name="exitAnimation" format="reference"/>
-        <attr name="animationDuration" format="integer"/>
-    </declare-styleable>
-
     <declare-styleable name="ContactBrowser">
         <attr name="contact_browser_list_padding_left" format="dimension"/>
         <attr name="contact_browser_list_padding_right" format="dimension"/>
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index e41adad..0d226d8 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -56,6 +56,7 @@
 import com.android.contacts.util.AccountFilterUtil;
 import com.android.contacts.util.AccountPromptUtils;
 import com.android.contacts.util.AccountsListAdapter;
+import com.android.contacts.util.UriUtils;
 import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
 import com.android.contacts.util.Constants;
 import com.android.contacts.util.DialogManager;
@@ -160,7 +161,8 @@
 
     private View mFavoritesView;
     private View mBrowserView;
-    private TransitionAnimationView mDetailsView;
+    private TransitionAnimationView mContactDetailsView;
+    private TransitionAnimationView mGroupDetailsView;
 
     private View mAddGroupImageView;
 
@@ -393,7 +395,8 @@
 
             // Container views for fragments
             mFavoritesView = getView(R.id.favorites_view);
-            mDetailsView = getView(R.id.details_view);
+            mContactDetailsView = getView(R.id.contact_details_view);
+            mGroupDetailsView = getView(R.id.group_details_view);
             mBrowserView = getView(R.id.browse_view);
 
             // 2-pane only fragments
@@ -416,7 +419,8 @@
 
             // Configure contact details
             mContactDetailLayoutController = new ContactDetailLayoutController(this, savedState,
-                    getFragmentManager(), mDetailsView, findViewById(R.id.contact_detail_container),
+                    getFragmentManager(), mContactDetailsView,
+                    findViewById(R.id.contact_detail_container),
                     new ContactDetailFragmentListener());
         }
         transaction.commitAllowingStateLoss();
@@ -592,6 +596,11 @@
     }
 
     private void setupGroupDetailFragment(Uri groupUri) {
+        // If we are switching from one group to another, do a cross-fade
+        if (mGroupDetailFragment != null && mGroupDetailFragment.getGroupUri() != null &&
+                !UriUtils.areEqual(mGroupDetailFragment.getGroupUri(), groupUri)) {
+            mGroupDetailsView.startTransition(mGroupDetailFragment.getView(), false);
+        }
         mGroupDetailFragment.loadGroup(groupUri);
         invalidateOptionsMenuIfNeeded();
     }
@@ -664,18 +673,21 @@
             case FAVORITES:
                 mFavoritesView.setVisibility(View.VISIBLE);
                 mBrowserView.setVisibility(View.GONE);
-                mDetailsView.setVisibility(View.GONE);
+                mGroupDetailsView.setVisibility(View.GONE);
+                mContactDetailsView.setVisibility(View.GONE);
                 break;
             case GROUPS:
                 mFavoritesView.setVisibility(View.GONE);
                 mBrowserView.setVisibility(View.VISIBLE);
-                mDetailsView.setVisibility(View.VISIBLE);
+                mGroupDetailsView.setVisibility(View.VISIBLE);
+                mContactDetailsView.setVisibility(View.GONE);
                 mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
                 break;
             case ALL:
                 mFavoritesView.setVisibility(View.GONE);
                 mBrowserView.setVisibility(View.VISIBLE);
-                mDetailsView.setVisibility(View.VISIBLE);
+                mContactDetailsView.setVisibility(View.VISIBLE);
+                mGroupDetailsView.setVisibility(View.GONE);
                 break;
         }
         FragmentManager fragmentManager = getFragmentManager();
diff --git a/src/com/android/contacts/detail/ContactDetailLayoutController.java b/src/com/android/contacts/detail/ContactDetailLayoutController.java
index b79ccc0..a87f97e 100644
--- a/src/com/android/contacts/detail/ContactDetailLayoutController.java
+++ b/src/com/android/contacts/detail/ContactDetailLayoutController.java
@@ -250,20 +250,25 @@
     public void setContactData(ContactLoader.Result data) {
         final boolean contactWasLoaded;
         final boolean contactHadUpdates;
+        final boolean isDifferentContact;
         if (mContactData == null) {
             contactHadUpdates = false;
             contactWasLoaded = false;
+            isDifferentContact = true;
         } else {
             contactHadUpdates = mContactHasUpdates;
             contactWasLoaded = true;
+            isDifferentContact =
+                    !UriUtils.areEqual(mContactData.getLookupUri(), data.getLookupUri());
         }
         mContactData = data;
         mContactHasUpdates = !data.getStreamItems().isEmpty();
 
         if (PhoneCapabilityTester.isUsingTwoPanes(mActivity)) {
             // Tablet: If we already showed data before, we want to cross-fade from screen to screen
-            if (contactWasLoaded && mTransitionAnimationView != null) {
-                mTransitionAnimationView.startTransition(mViewContainer, mContactData == null);
+            if (contactWasLoaded && mTransitionAnimationView != null && isDifferentContact) {
+                mTransitionAnimationView.startTransition(
+                        mViewContainer, mContactData == null);
             }
         } else {
             // Small screen: We are on our own screen. Fade the data in, but only the first time
@@ -326,9 +331,12 @@
 
         switch (mLayoutMode) {
             case TWO_COLUMN: {
-                // This is screen is very hard to animate properly, because there is such a hard
-                // cut from the regular version. A proper animation would have to reflow text and
-                // move things around. No animation for now
+                if (!isDifferentContact && animateStateChange) {
+                    // This is screen is very hard to animate properly, because there is such a hard
+                    // cut from the regular version. A proper animation would have to reflow text
+                    // and move things around. Doing a simple cross-fade instead.
+                    mTransitionAnimationView.startTransition(mViewContainer, false);
+                }
 
                 // Set the contact data (hide the static photo because the photo will already be in
                 // the header that scrolls with contact details).
diff --git a/src/com/android/contacts/group/GroupDetailFragment.java b/src/com/android/contacts/group/GroupDetailFragment.java
index 3a43e66..b4f642a 100644
--- a/src/com/android/contacts/group/GroupDetailFragment.java
+++ b/src/com/android/contacts/group/GroupDetailFragment.java
@@ -191,6 +191,10 @@
         mShowGroupActionInActionBar = show;
     }
 
+    public Uri getGroupUri() {
+        return mGroupUri;
+    }
+
     /**
      * Start the loader to retrieve the metadata for this group.
      */
diff --git a/src/com/android/contacts/widget/TransitionAnimationView.java b/src/com/android/contacts/widget/TransitionAnimationView.java
index c70ca25..cefc82d 100644
--- a/src/com/android/contacts/widget/TransitionAnimationView.java
+++ b/src/com/android/contacts/widget/TransitionAnimationView.java
@@ -15,17 +15,13 @@
  */
 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.animation.ObjectAnimator;
 import android.content.Context;
-import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.util.AttributeSet;
 import android.view.View;
@@ -33,23 +29,16 @@
 
 /**
  * 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:
+ * After rebinding the contents, the following call should be made (where child is the only visible)
+ * child
  * <pre>
- *   TransitionAnimationView.startAnimation(this);
+ *   TransitionAnimationView.startAnimation(child);
  * </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;
+    private ObjectAnimator mPreviousAnimator;
 
     public TransitionAnimationView(Context context) {
         this(context, null, 0);
@@ -61,67 +50,16 @@
 
     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.animator.fade_in);
-        mExitAnimationId = a.getResourceId(R.styleable.TransitionAnimationView_exitAnimation,
-                android.R.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);
-        mEnterAnimation.setDuration(mAnimationDuration);
-
-        mExitAnimation = AnimatorInflater.loadAnimator(getContext(), mExitAnimationId);
-        if (mExitAnimation == null) {
-            throw new IllegalArgumentException("Invalid exit animation: " + mExitAnimationId);
-        }
-        mExitAnimation.setDuration(mAnimationDuration);
     }
 
     @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        if (changed || mPreviousStateBitmap == null) {
-            if (mPreviousStateBitmap != null) {
-                mPreviousStateBitmap.recycle();
-                mPreviousStateBitmap = null;
-            }
-            int width = right - left;
-            int height = bottom - top;
-            if (width > 0 && height > 0) {
-                mPreviousStateBitmap = Bitmap.createBitmap(
-                        width, height, Bitmap.Config.ARGB_8888);
-                mPreviousStateView.setBackgroundDrawable(
-                        new BitmapDrawable(getContext().getResources(), mPreviousStateBitmap));
-                mClipRect.set(mClipMargins.left, mClipMargins.top,
-                        width - mClipMargins.right, height - mClipMargins.bottom);
-            } else {
-                mPreviousStateBitmap = null;
-                mPreviousStateView.setBackgroundDrawable(null);
-            }
-        }
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mPreviousStateView = new View(getContext());
+        mPreviousStateView.setVisibility(View.INVISIBLE);
+        mPreviousStateView.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+                LayoutParams.MATCH_PARENT));
+        addView(mPreviousStateView);
     }
 
     @Override
@@ -135,40 +73,46 @@
     }
 
     public void startTransition(View view, boolean closing) {
-        if (mEnterAnimation.isRunning()) {
-            mEnterAnimation.end();
-        }
-        if (mExitAnimation.isRunning()) {
-            mExitAnimation.end();
+        if (mPreviousAnimator != null && mPreviousAnimator.isRunning()) {
+            mPreviousAnimator.end();
         }
         if (view.getVisibility() != View.VISIBLE) {
             if (!closing) {
-                mEnterAnimation.setTarget(view);
-                mEnterAnimation.start();
+                mPreviousAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 0.0f, 1.0f);
+                mPreviousAnimator.start();
             }
         } else if (closing) {
-            mExitAnimation.setTarget(view);
-            mExitAnimation.start();
+            mPreviousAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 1.0f, 0.0f);
+            mPreviousAnimator.start();
         } else {
-            if (mPreviousStateBitmap == null) {
-                return;
+            if (view.getWidth() > 0 && view.getHeight() > 0) {
+                // Take a "screenshot" of the current state of the screen and show that on top
+                // of the real content. Then, fade that out.
+                mPreviousStateBitmap = Bitmap.createBitmap(
+                        view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
+                mPreviousStateView.setBackgroundDrawable(
+                        new BitmapDrawable(getContext().getResources(), mPreviousStateBitmap));
+                mPreviousStateView.setLayoutParams(view.getLayoutParams());
+                mPreviousStateBitmap.eraseColor(Color.WHITE);
+                Canvas canvas = new Canvas(mPreviousStateBitmap);
+                view.draw(canvas);
+                canvas.setBitmap(null);
+                mPreviousStateView.setVisibility(View.VISIBLE);
+
+                mPreviousAnimator =
+                        ObjectAnimator.ofFloat(mPreviousStateView, View.ALPHA, 1.0f, 0.0f);
+                mPreviousAnimator.start();
             }
-
-            mPreviousStateBitmap.eraseColor(Color.TRANSPARENT);
-            Canvas canvas = new Canvas(mPreviousStateBitmap);
-            canvas.clipRect(mClipRect);
-            view.draw(canvas);
-            canvas.setBitmap(null);
-            mPreviousStateView.setVisibility(View.VISIBLE);
-
-            mEnterAnimation.setTarget(view);
-            mEnterAnimation.start();
         }
     }
 
     @Override
     public void onAnimationEnd(Animator animation) {
         mPreviousStateView.setVisibility(View.INVISIBLE);
+        mPreviousStateView.setBackgroundDrawable(null);
+        mPreviousStateBitmap.recycle();
+        mPreviousStateBitmap = null;
+        mPreviousAnimator = null;
     }
 
     @Override