Merge "Remove call to hidden suppressLayout()"
diff --git a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
index 3f8a477..70e8f37 100644
--- a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
+++ b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
@@ -15,6 +15,9 @@
  */
 package com.android.contacts.quickcontact;
 
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.Intent;
@@ -26,7 +29,6 @@
 import android.text.Spannable;
 import android.text.TextUtils;
 import android.transition.ChangeBounds;
-import android.transition.ChangeScroll;
 import android.transition.Fade;
 import android.transition.Transition;
 import android.transition.Transition.TransitionListener;
@@ -34,6 +36,7 @@
 import android.transition.TransitionSet;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.Property;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -42,6 +45,7 @@
 import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
@@ -57,11 +61,28 @@
 
     private static final String TAG = "ExpandingEntryCardView";
     private static final int DURATION_EXPAND_ANIMATION_FADE_IN = 200;
+    private static final int DURATION_COLLAPSE_ANIMATION_FADE_OUT = 75;
     private static final int DELAY_EXPAND_ANIMATION_FADE_IN = 100;
 
     public static final int DURATION_EXPAND_ANIMATION_CHANGE_BOUNDS = 300;
     public static final int DURATION_COLLAPSE_ANIMATION_CHANGE_BOUNDS = 300;
 
+    private static final Property<View, Integer> VIEW_LAYOUT_HEIGHT_PROPERTY =
+            new Property<View, Integer>(Integer.class, "height") {
+                @Override
+                public void set(View view, Integer height) {
+                    LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)
+                            view.getLayoutParams();
+                    params.height = height;
+                    view.setLayoutParams(params);
+                }
+
+                @Override
+                public Integer get(View view) {
+                    return view.getLayoutParams().height;
+                }
+            };
+
     /**
      * Entry data.
      */
@@ -194,7 +215,8 @@
 
     public interface ExpandingEntryCardViewListener {
         void onCollapse(int heightDelta);
-        void onExpand(int heightDelta);
+        void onExpand();
+        void onExpandDone();
     }
 
     private View mExpandCollapseButton;
@@ -341,10 +363,34 @@
         mOnCreateContextMenuListener = listener;
     }
 
+    private List<View> calculateEntriesToRemoveDuringCollapse() {
+        final List<View> viewsToRemove = getViewsToDisplay(true);
+        final List<View> viewsCollapsed = getViewsToDisplay(false);
+        viewsToRemove.removeAll(viewsCollapsed);
+        return viewsToRemove;
+    }
+
     private void insertEntriesIntoViewGroup() {
         mEntriesViewGroup.removeAllViews();
 
-        if (mIsExpanded) {
+        for (View view : getViewsToDisplay(mIsExpanded)) {
+            mEntriesViewGroup.addView(view);
+        }
+
+        removeView(mExpandCollapseButton);
+        if (mCollapsedEntriesCount < mNumEntries
+                && mExpandCollapseButton.getParent() == null && !mIsAlwaysExpanded) {
+            mContainer.addView(mExpandCollapseButton, -1);
+        }
+    }
+
+    /**
+     * Returns the list of views that should be displayed. This changes depending on whether
+     * the card is expanded or collapsed.
+     */
+    private List<View> getViewsToDisplay(boolean isExpanded) {
+        final List<View> viewsToDisplay = new ArrayList<View>();
+        if (isExpanded) {
             for (int i = 0; i < mEntryViews.size(); i++) {
                 List<View> viewList = mEntryViews.get(i);
                 if (i > 0) {
@@ -355,10 +401,10 @@
                     } else {
                         separator = mSeparators.get(i - 1);
                     }
-                    mEntriesViewGroup.addView(separator);
+                    viewsToDisplay.add(separator);
                 }
                 for (View view : viewList) {
-                    addEntry(view);
+                    viewsToDisplay.add(view);
                 }
             }
         } else {
@@ -378,33 +424,31 @@
                     } else {
                         separator = mSeparators.get(i - 1);
                     }
-                    mEntriesViewGroup.addView(separator);
+                    viewsToDisplay.add(separator);
                 }
-                addEntry(entryViewList.get(0));
+                viewsToDisplay.add(entryViewList.get(0));
                 numInViewGroup++;
                 // Insert entries in this list to hit mCollapsedEntriesCount.
                 for (int j = 1;
                         j < entryViewList.size() && numInViewGroup < mCollapsedEntriesCount &&
                         extraEntries > 0;
                         j++) {
-                    addEntry(entryViewList.get(j));
+                    viewsToDisplay.add(entryViewList.get(j));
                     numInViewGroup++;
                     extraEntries--;
                 }
             }
         }
 
-        removeView(mExpandCollapseButton);
-        if (mCollapsedEntriesCount < mNumEntries
-                && mExpandCollapseButton.getParent() == null && !mIsAlwaysExpanded) {
-            mContainer.addView(mExpandCollapseButton, -1);
-        }
+        formatEntryIfFirst(viewsToDisplay);
+        return viewsToDisplay;
     }
 
-    private void addEntry(View entry) {
+    private void formatEntryIfFirst(List<View> entriesViewGroup) {
         // If no title and the first entry in the group, add extra padding
         if (TextUtils.isEmpty(mTitleTextView.getText()) &&
-                mEntriesViewGroup.getChildCount() == 0) {
+                entriesViewGroup.size() > 0) {
+            final View entry = entriesViewGroup.get(0);
             entry.setPadding(entry.getPaddingLeft(),
                     getResources().getDimensionPixelSize(
                             R.dimen.expanding_entry_card_item_padding_top) +
@@ -413,7 +457,6 @@
                     entry.getPaddingRight(),
                     entry.getPaddingBottom());
         }
-        mEntriesViewGroup.addView(entry);
     }
 
     private View generateSeparator(View entry) {
@@ -770,12 +813,12 @@
         transitionSet.addListener(new TransitionListener() {
             @Override
             public void onTransitionStart(Transition transition) {
-                // The listener is used to turn off suppressing, the proper delta is not necessary
-                mListener.onExpand(0);
+                mListener.onExpand();
             }
 
             @Override
             public void onTransitionEnd(Transition transition) {
+                mListener.onExpandDone();
             }
 
             @Override
@@ -802,57 +845,59 @@
     }
 
     private void collapse() {
-        final int startingHeight = mEntriesViewGroup.getMeasuredHeight();
-        mIsExpanded = false;
-        updateExpandCollapseButton(getExpandButtonText(),
-                DURATION_COLLAPSE_ANIMATION_CHANGE_BOUNDS);
+        final List<View> views = calculateEntriesToRemoveDuringCollapse();
 
-        final ChangeBounds boundsTransition = new ChangeBounds();
-        boundsTransition.setDuration(DURATION_COLLAPSE_ANIMATION_CHANGE_BOUNDS);
-
-        final ChangeScroll scrollTransition = new ChangeScroll();
-        scrollTransition.setDuration(DURATION_COLLAPSE_ANIMATION_CHANGE_BOUNDS);
-
-        TransitionSet transitionSet = new TransitionSet();
-        transitionSet.addTransition(boundsTransition);
-        transitionSet.addTransition(scrollTransition);
-
-        transitionSet.excludeTarget(R.id.text, /* exclude = */ true);
-
-        final ViewGroup transitionViewContainer = mAnimationViewGroup == null ?
-                this : mAnimationViewGroup;
-
-        boundsTransition.addListener(new TransitionListener() {
+        // This animation requires layout changes, unlike the expand() animation: the action bar
+        // might get scrolled open in order to fill empty space. As a result, we can't use
+        // ChangeBounds here. Instead manually animate view height and alpha. This isn't as
+        // efficient as the bounds and translation changes performed by ChangeBounds. Nonetheless, a
+        // reasonable frame-rate is achieved collapsing a dozen elements on a user Svelte N4. So the
+        // performance hit doesn't justify writing a less maintainable animation.
+        final AnimatorSet set = new AnimatorSet();
+        final List<Animator> animators = new ArrayList<Animator>(views.size());
+        int totalSizeChange = 0;
+        for (View viewToRemove : views) {
+            final ObjectAnimator animator = ObjectAnimator.ofObject(viewToRemove,
+                    VIEW_LAYOUT_HEIGHT_PROPERTY, null, viewToRemove.getHeight(), 0);
+            totalSizeChange += viewToRemove.getHeight();
+            animator.setDuration(DURATION_COLLAPSE_ANIMATION_CHANGE_BOUNDS);
+            animators.add(animator);
+            viewToRemove.animate().alpha(0).setDuration(DURATION_COLLAPSE_ANIMATION_FADE_OUT);
+        }
+        set.playTogether(animators);
+        set.start();
+        set.addListener(new AnimatorListener() {
             @Override
-            public void onTransitionStart(Transition transition) {
-                /*
-                 * onTransitionStart is called after the view hierarchy has been changed but before
-                 * the animation begins.
-                 */
-                int finishingHeight = mEntriesViewGroup.getMeasuredHeight();
-                mListener.onCollapse(startingHeight - finishingHeight);
+            public void onAnimationStart(Animator animation) {
             }
 
             @Override
-            public void onTransitionEnd(Transition transition) {
+            public void onAnimationEnd(Animator animation) {
+                // Now that the views have been animated away, actually remove them from the view
+                // hierarchy. Reset their appearance so that they look appropriate when they
+                // get added back later. We know that all views originally used WRAP_CONTENT
+                // since no separators are ever removed during collapse().
+                insertEntriesIntoViewGroup();
+                for (View view : views) {
+                    VIEW_LAYOUT_HEIGHT_PROPERTY.set(view, LayoutParams.WRAP_CONTENT);
+                    view.animate().cancel();
+                    view.setAlpha(1);
+                }
             }
 
             @Override
-            public void onTransitionCancel(Transition transition) {
+            public void onAnimationCancel(Animator animation) {
             }
 
             @Override
-            public void onTransitionPause(Transition transition) {
-            }
-
-            @Override
-            public void onTransitionResume(Transition transition) {
+            public void onAnimationRepeat(Animator animation) {
             }
         });
 
-        TransitionManager.beginDelayedTransition(transitionViewContainer, transitionSet);
-
-        insertEntriesIntoViewGroup();
+        mListener.onCollapse(totalSizeChange);
+        mIsExpanded = false;
+        updateExpandCollapseButton(getExpandButtonText(),
+                DURATION_COLLAPSE_ANIMATION_CHANGE_BOUNDS);
     }
 
     /**
@@ -892,7 +937,7 @@
                             getResources().getDimensionPixelSize(
                                     R.dimen.expanding_entry_card_null_title_top_extra_padding),
                     firstEntry.getPaddingRight(),
-                    firstEntry.getPaddingBottom());
+                    firgetPaddingBottom());
         }
     }
 
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 31a0c50..73a3db9 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -396,8 +396,13 @@
         }
 
         @Override
-        public void onExpand(int heightDelta) {
-            mScroller.prepareForExpandingScrollChild();
+        public void onExpand() {
+            mScroller.setDisableTouchesForSuppressLayout(/* areTouchesDisabled = */ true);
+        }
+
+        @Override
+        public void onExpandDone() {
+            mScroller.setDisableTouchesForSuppressLayout(/* areTouchesDisabled = */ false);
         }
     };
 
diff --git a/src/com/android/contacts/widget/MultiShrinkScroller.java b/src/com/android/contacts/widget/MultiShrinkScroller.java
index 7b97643..7c46a86 100644
--- a/src/com/android/contacts/widget/MultiShrinkScroller.java
+++ b/src/com/android/contacts/widget/MultiShrinkScroller.java
@@ -17,7 +17,6 @@
 import android.graphics.Color;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
 import android.hardware.display.DisplayManager;
 import android.os.Trace;
@@ -140,6 +139,7 @@
      */
     private boolean mHasEverTouchedTheTop;
     private boolean mIsTouchDisabledForDismissAnimation;
+    private boolean mIsTouchDisabledForSuppressLayout;
 
     private final Scroller mScroller;
     private final EdgeEffect mEdgeGlowBottom;
@@ -405,7 +405,8 @@
     }
 
     private boolean shouldStartDrag(MotionEvent event) {
-        if (mIsTouchDisabledForDismissAnimation) return false;
+        if (mIsTouchDisabledForDismissAnimation || mIsTouchDisabledForSuppressLayout) return false;
+
 
         if (mIsBeingDragged) {
             mIsBeingDragged = false;
@@ -441,7 +442,7 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (mIsTouchDisabledForDismissAnimation) return true;
+        if (mIsTouchDisabledForDismissAnimation || mIsTouchDisabledForSuppressLayout) return true;
 
         final int action = event.getAction();
 
@@ -1285,12 +1286,6 @@
      * space at the bottom of this ViewGroup.
      */
     public void prepareForShrinkingScrollChild(int heightDelta) {
-        // The Transition framework may suppress layout on the scene root and its children. If
-        // mScrollView has its layout suppressed, user scrolling interactions will not display
-        // correctly. By turning suppress off for mScrollView, mScrollView properly adjusts its
-        // graphics as the user scrolls during the transition.
-        mScrollView.suppressLayout(false);
-
         final int newEmptyScrollViewSpace = -getOverflowingChildViewSize() + heightDelta;
         if (newEmptyScrollViewSpace > 0 && !mIsTwoPanel) {
             final int newDesiredToolbarHeight = Math.min(getToolbarHeight()
@@ -1300,11 +1295,13 @@
         }
     }
 
-    public void prepareForExpandingScrollChild() {
-        // The Transition framework may suppress layout on the scene root and its children. If
-        // mScrollView has its layout suppressed, user scrolling interactions will not display
-        // correctly. By turning suppress off for mScrollView, mScrollView properly adjusts its
-        // graphics as the user scrolls during the transition.
-        mScrollView.suppressLayout(false);
+    /**
+     * If {@param areTouchesDisabled} is TRUE, ignore all of the user's touches.
+     */
+    public void setDisableTouchesForSuppressLayout(boolean areTouchesDisabled) {
+        // The card expansion animation uses the Transition framework's ChangeBounds API. This
+        // invokes suppressLayout(true) on the MultiShrinkScroller. As a result, we need to avoid
+        // all layout changes during expansion in order to avoid weird layout artifacts.
+        mIsTouchDisabledForSuppressLayout = areTouchesDisabled;
     }
 }