Improve transition when going to Wifi/BT detail panel

Animate the height change of the lower QS panel when going into
Wifi/BT or any other panel which is higher than the default size.

Bug: 18665311
Change-Id: Ic1d1f19183cf6b502d6b623388b315ed6e0a3c9f
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index c76d442..1873168 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<FrameLayout
+<com.android.systemui.qs.QSContainer
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/quick_settings_container"
         android:layout_width="match_parent"
@@ -28,4 +28,4 @@
             android:background="#0000"
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />
-</FrameLayout>
+</com.android.systemui.qs.QSContainer>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
new file mode 100644
index 0000000..cfe8d07
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2014 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.systemui.qs;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.systemui.R;
+
+/**
+ * Wrapper view with background which contains {@link QSPanel}
+ */
+public class QSContainer extends FrameLayout {
+
+    private int mHeightOverride = -1;
+    private QSPanel mQSPanel;
+
+    public QSContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mQSPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        updateBottom();
+    }
+
+    /**
+     * Overrides the height of this view (post-layout), so that the content is clipped to that
+     * height and the background is set to that height.
+     *
+     * @param heightOverride the overridden height
+     */
+    public void setHeightOverride(int heightOverride) {
+        mHeightOverride = heightOverride;
+        updateBottom();
+    }
+
+    /**
+     * The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
+     * during closing the detail panel, this already returns the smaller height.
+     */
+    public int getDesiredHeight() {
+        if (mQSPanel.isClosingDetail()) {
+            return mQSPanel.getGridHeight() + getPaddingTop() + getPaddingBottom();
+        } else {
+            return getMeasuredHeight();
+        }
+    }
+
+    private void updateBottom() {
+        int height = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
+        setBottom(getTop() + height);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 91b1569..974235e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -67,8 +67,10 @@
     private int mPanelPaddingBottom;
     private int mDualTileUnderlap;
     private int mBrightnessPaddingTop;
+    private int mGridHeight;
     private boolean mExpanded;
     private boolean mListening;
+    private boolean mClosingDetail;
 
     private Record mDetailRecord;
     private Callback mCallback;
@@ -320,6 +322,14 @@
         showDetail(false, mDetailRecord);
     }
 
+    public boolean isClosingDetail() {
+        return mClosingDetail;
+    }
+
+    public int getGridHeight() {
+        return mGridHeight;
+    }
+
     private void handleShowDetail(Record r, boolean show) {
         if (r instanceof TileRecord) {
             handleShowDetailTile((TileRecord) r, show);
@@ -364,6 +374,7 @@
             setDetailRecord(r);
             listener = mHideGridContentWhenDone;
         } else {
+            mClosingDetail = true;
             setGridContentVisibility(true);
             listener = mTeardownDetailWhenDone;
             fireScanStateChanged(false);
@@ -426,6 +437,7 @@
         if (mDetail.getMeasuredHeight() < h) {
             mDetail.measure(exactly(width), exactly(h));
         }
+        mGridHeight = h;
         setMeasuredDimension(width, Math.max(h, mDetail.getMeasuredHeight()));
     }
 
@@ -537,6 +549,7 @@
         public void onAnimationEnd(Animator animation) {
             mDetailContent.removeAllViews();
             setDetailRecord(null);
+            mClosingDetail = false;
         };
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index d2dc425..1b00e59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -40,6 +40,7 @@
 
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.R;
+import com.android.systemui.qs.QSContainer;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -71,7 +72,7 @@
     private StatusBarHeaderView mHeader;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private KeyguardStatusBarView mKeyguardStatusBar;
-    private View mQsContainer;
+    private QSContainer mQsContainer;
     private QSPanel mQsPanel;
     private KeyguardStatusView mKeyguardStatusView;
     private ObservableScrollView mScrollView;
@@ -161,6 +162,7 @@
     private boolean mKeyguardStatusViewAnimating;
     private boolean mHeaderAnimatingIn;
     private ObjectAnimator mQsContainerAnimator;
+    private ValueAnimator mQsSizeChangeAnimator;
 
     private boolean mShadeEmpty;
 
@@ -188,7 +190,7 @@
         mHeader.setOnClickListener(this);
         mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
         mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view);
-        mQsContainer = findViewById(R.id.quick_settings_container);
+        mQsContainer = (QSContainer) findViewById(R.id.quick_settings_container);
         mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
         mClockView = (TextView) findViewById(R.id.clock_view);
         mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
@@ -283,21 +285,35 @@
         mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
 
         // Calculate quick setting heights.
+        int oldMaxHeight = mQsMaxExpansionHeight;
         mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight;
-        mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
+        mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getDesiredHeight();
         positionClockAndNotifications();
-        if (mQsExpanded) {
-            if (mQsFullyExpanded) {
-                mQsExpansionHeight = mQsMaxExpansionHeight;
-                requestScrollerTopPaddingUpdate(false /* animate */);
+        if (mQsExpanded && mQsFullyExpanded) {
+            mQsExpansionHeight = mQsMaxExpansionHeight;
+            requestScrollerTopPaddingUpdate(false /* animate */);
+            requestPanelHeightUpdate();
+
+            // Size has changed, start an animation.
+            if (mQsMaxExpansionHeight != oldMaxHeight) {
+                startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
             }
-        } else {
+        } else if (!mQsExpanded) {
             setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
-            mNotificationStackScroller.setStackHeight(getExpandedHeight());
         }
+        mNotificationStackScroller.setStackHeight(getExpandedHeight());
         updateHeader();
         mNotificationStackScroller.updateIsSmallScreen(
                 mHeader.getCollapsedHeight() + mQsPeekHeight);
+
+        // If we are running a size change animation, the animation takes care of the height of
+        // the container. However, if we are not animating, we always need to make the QS container
+        // the desired height so when closing the QS detail, it stays smaller after the size change
+        // animation is finished but the detail view is still being animated away (this animation
+        // takes longer than the size change animation).
+        if (mQsSizeChangeAnimator == null) {
+            mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight());
+        }
     }
 
     @Override
@@ -310,6 +326,32 @@
         mSecureCameraLaunchManager.destroy();
     }
 
+    private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
+        if (mQsSizeChangeAnimator != null) {
+            oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
+            mQsSizeChangeAnimator.cancel();
+        }
+        mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
+        mQsSizeChangeAnimator.setDuration(300);
+        mQsSizeChangeAnimator.setInterpolator(mFastOutSlowInInterpolator);
+        mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                requestScrollerTopPaddingUpdate(false /* animate */);
+                requestPanelHeightUpdate();
+                int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
+                mQsContainer.setHeightOverride(height - mHeader.getExpandedHeight());
+            }
+        });
+        mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mQsSizeChangeAnimator = null;
+            }
+        });
+        mQsSizeChangeAnimator.start();
+    }
+
     /**
      * Positions the clock and notifications dynamically depending on how many notifications are
      * showing.
@@ -1113,7 +1155,7 @@
 
     private void setQsTranslation(float height) {
         if (!mHeaderAnimatingIn) {
-            mQsContainer.setY(height - mQsContainer.getHeight() + getHeaderTranslation());
+            mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation());
         }
         if (mKeyguardShowing) {
             mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0));
@@ -1137,6 +1179,8 @@
                     : maxQs;
             return (int) interpolate(getExpandedFraction(),
                     mQsMinExpansionHeight, max);
+        } else if (mQsSizeChangeAnimator != null) {
+            return (int) mQsSizeChangeAnimator.getAnimatedValue();
         } else if (mKeyguardShowing && mScrollYOverride == -1) {
 
             // We can only do the smoother transition on Keyguard when we also are not collapsing
@@ -1348,14 +1392,20 @@
                     + mNotificationStackScroller.getBottomStackPeekSize()
                     + mNotificationStackScroller.getCollapseSecondCardPadding();
         }
+        int maxQsHeight = mQsMaxExpansionHeight;
+
+        // If an animation is changing the size of the QS panel, take the animated value.
+        if (mQsSizeChangeAnimator != null) {
+            maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
+        }
         float totalHeight = Math.max(
-                mQsMaxExpansionHeight + mNotificationStackScroller.getNotificationTopPadding(),
+                maxQsHeight + mNotificationStackScroller.getNotificationTopPadding(),
                 mStatusBarState == StatusBarState.KEYGUARD
                         ? mClockPositionResult.stackScrollerPadding - mTopPaddingAdjustment
                         : 0)
                 + notificationHeight;
         if (totalHeight > mNotificationStackScroller.getHeight()) {
-            float fullyCollapsedHeight = mQsMaxExpansionHeight
+            float fullyCollapsedHeight = maxQsHeight
                     + mNotificationStackScroller.getMinStackHeight()
                     + mNotificationStackScroller.getNotificationTopPadding()
                     - getScrollViewScrollY();