• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.qs;
16 
17 import android.animation.TimeInterpolator;
18 import android.animation.ValueAnimator;
19 import android.util.Log;
20 import android.view.View;
21 import android.view.View.OnAttachStateChangeListener;
22 import android.view.View.OnLayoutChangeListener;
23 
24 import com.android.systemui.dagger.qualifiers.Main;
25 import com.android.systemui.plugins.qs.QS;
26 import com.android.systemui.plugins.qs.QSTile;
27 import com.android.systemui.plugins.qs.QSTileView;
28 import com.android.systemui.qs.PagedTileLayout.PageListener;
29 import com.android.systemui.qs.QSHost.Callback;
30 import com.android.systemui.qs.QSPanel.QSTileLayout;
31 import com.android.systemui.qs.TouchAnimator.Builder;
32 import com.android.systemui.qs.TouchAnimator.Listener;
33 import com.android.systemui.qs.dagger.QSScope;
34 import com.android.systemui.qs.tileimpl.HeightOverrideable;
35 import com.android.systemui.statusbar.CrossFadeHelper;
36 import com.android.systemui.tuner.TunerService;
37 import com.android.systemui.tuner.TunerService.Tunable;
38 import com.android.wm.shell.animation.Interpolators;
39 
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.List;
43 import java.util.concurrent.Executor;
44 
45 import javax.inject.Inject;
46 
47 /** */
48 @QSScope
49 public class QSAnimator implements Callback, PageListener, Listener, OnLayoutChangeListener,
50         OnAttachStateChangeListener, Tunable {
51 
52     private static final String TAG = "QSAnimator";
53 
54     private static final String ALLOW_FANCY_ANIMATION = "sysui_qs_fancy_anim";
55     private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows";
56 
57     public static final float EXPANDED_TILE_DELAY = .86f;
58     public static final float SHORT_PARALLAX_AMOUNT = 0.1f;
59     private static final long QQS_FADE_IN_DURATION = 200L;
60     // Fade out faster than fade in to finish before QQS hides.
61     private static final long QQS_FADE_OUT_DURATION = 50L;
62 
63 
64     private final ArrayList<View> mAllViews = new ArrayList<>();
65     /**
66      * List of {@link View}s representing Quick Settings that are being animated from the quick QS
67      * position to the normal QS panel. These views will only show once the animation is complete,
68      * to prevent overlapping of semi transparent views
69      */
70     private final ArrayList<View> mQuickQsViews = new ArrayList<>();
71     private final QuickQSPanel mQuickQsPanel;
72     private final QSPanelController mQsPanelController;
73     private final QuickQSPanelController mQuickQSPanelController;
74     private final QuickStatusBarHeader mQuickStatusBarHeader;
75     private final QSSecurityFooter mSecurityFooter;
76     private final QS mQs;
77 
78     private PagedTileLayout mPagedLayout;
79 
80     private boolean mOnFirstPage = true;
81     private QSExpansionPathInterpolator mQSExpansionPathInterpolator;
82     private TouchAnimator mFirstPageAnimator;
83     private TouchAnimator mFirstPageDelayedAnimator;
84     private TouchAnimator mTranslationXAnimator;
85     private TouchAnimator mTranslationYAnimator;
86     private TouchAnimator mNonfirstPageAnimator;
87     private TouchAnimator mNonfirstPageDelayedAnimator;
88     // This animates fading of SecurityFooter and media divider
89     private TouchAnimator mAllPagesDelayedAnimator;
90     private TouchAnimator mBrightnessAnimator;
91     private HeightExpansionAnimator mQQSTileHeightAnimator;
92     private HeightExpansionAnimator mOtherTilesExpandAnimator;
93 
94     private boolean mNeedsAnimatorUpdate = false;
95     private boolean mToShowing;
96     private boolean mOnKeyguard;
97 
98     private boolean mAllowFancy;
99     private boolean mFullRows;
100     private int mNumQuickTiles;
101     private float mLastPosition;
102     private final QSTileHost mHost;
103     private final Executor mExecutor;
104     private final TunerService mTunerService;
105     private boolean mShowCollapsedOnKeyguard;
106     private boolean mTranslateWhileExpanding;
107 
108     @Inject
QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader, QSPanelController qsPanelController, QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost, QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService, QSExpansionPathInterpolator qsExpansionPathInterpolator)109     public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
110             QSPanelController qsPanelController,
111             QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
112             QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService,
113             QSExpansionPathInterpolator qsExpansionPathInterpolator) {
114         mQs = qs;
115         mQuickQsPanel = quickPanel;
116         mQsPanelController = qsPanelController;
117         mQuickQSPanelController = quickQSPanelController;
118         mQuickStatusBarHeader = quickStatusBarHeader;
119         mSecurityFooter = securityFooter;
120         mHost = qsTileHost;
121         mExecutor = executor;
122         mTunerService = tunerService;
123         mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
124         mHost.addCallback(this);
125         mQsPanelController.addOnAttachStateChangeListener(this);
126         qs.getView().addOnLayoutChangeListener(this);
127         if (mQsPanelController.isAttachedToWindow()) {
128             onViewAttachedToWindow(null);
129         }
130         QSTileLayout tileLayout = mQsPanelController.getTileLayout();
131         if (tileLayout instanceof PagedTileLayout) {
132             mPagedLayout = ((PagedTileLayout) tileLayout);
133         } else {
134             Log.w(TAG, "QS Not using page layout");
135         }
136         mQsPanelController.setPageListener(this);
137     }
138 
onRtlChanged()139     public void onRtlChanged() {
140         updateAnimators();
141     }
142 
143     /**
144      * Request an update to the animators. This will update them lazily next time the position
145      * is changed.
146      */
requestAnimatorUpdate()147     public void requestAnimatorUpdate() {
148         mNeedsAnimatorUpdate = true;
149     }
150 
setOnKeyguard(boolean onKeyguard)151     public void setOnKeyguard(boolean onKeyguard) {
152         mOnKeyguard = onKeyguard;
153         updateQQSVisibility();
154         if (mOnKeyguard) {
155             clearAnimationState();
156         }
157     }
158 
startAlphaAnimation(boolean show)159     void startAlphaAnimation(boolean show) {
160         if (show == mToShowing) {
161             return;
162         }
163         mToShowing = show;
164         if (show) {
165             CrossFadeHelper.fadeIn(mQs.getView(), QQS_FADE_IN_DURATION, 0 /* delay */);
166         } else {
167             CrossFadeHelper.fadeOut(mQs.getView(), QQS_FADE_OUT_DURATION, 0 /* delay */,
168                     null /* endRunnable */);
169         }
170     }
171 
172     /**
173      * Sets whether or not the keyguard is currently being shown with a collapsed header.
174      */
setShowCollapsedOnKeyguard(boolean showCollapsedOnKeyguard)175     void setShowCollapsedOnKeyguard(boolean showCollapsedOnKeyguard) {
176         mShowCollapsedOnKeyguard = showCollapsedOnKeyguard;
177         updateQQSVisibility();
178         setCurrentPosition();
179     }
180 
181 
setCurrentPosition()182     private void setCurrentPosition() {
183         setPosition(mLastPosition);
184     }
185 
updateQQSVisibility()186     private void updateQQSVisibility() {
187         mQuickQsPanel.setVisibility(mOnKeyguard
188                 && !mShowCollapsedOnKeyguard ? View.INVISIBLE : View.VISIBLE);
189     }
190 
191     @Override
onViewAttachedToWindow(View v)192     public void onViewAttachedToWindow(View v) {
193         mTunerService.addTunable(this, ALLOW_FANCY_ANIMATION,
194                 MOVE_FULL_ROWS);
195     }
196 
197     @Override
onViewDetachedFromWindow(View v)198     public void onViewDetachedFromWindow(View v) {
199         mHost.removeCallback(this);
200         mTunerService.removeTunable(this);
201     }
202 
203     @Override
onTuningChanged(String key, String newValue)204     public void onTuningChanged(String key, String newValue) {
205         if (ALLOW_FANCY_ANIMATION.equals(key)) {
206             mAllowFancy = TunerService.parseIntegerSwitch(newValue, true);
207             if (!mAllowFancy) {
208                 clearAnimationState();
209             }
210         } else if (MOVE_FULL_ROWS.equals(key)) {
211             mFullRows = TunerService.parseIntegerSwitch(newValue, true);
212         }
213         updateAnimators();
214     }
215 
216     @Override
onPageChanged(boolean isFirst)217     public void onPageChanged(boolean isFirst) {
218         if (mOnFirstPage == isFirst) return;
219         if (!isFirst) {
220             clearAnimationState();
221         }
222         mOnFirstPage = isFirst;
223     }
224 
translateContent( View qqsView, View qsView, View commonParent, int xOffset, int yOffset, int[] temp, TouchAnimator.Builder animatorBuilderX, TouchAnimator.Builder animatorBuilderY )225     private void translateContent(
226             View qqsView,
227             View qsView,
228             View commonParent,
229             int xOffset,
230             int yOffset,
231             int[] temp,
232             TouchAnimator.Builder animatorBuilderX,
233             TouchAnimator.Builder animatorBuilderY
234     ) {
235         getRelativePosition(temp, qqsView, commonParent);
236         int qqsPosX = temp[0];
237         int qqsPosY = temp[1];
238         getRelativePosition(temp, qsView, commonParent);
239         int qsPosX = temp[0];
240         int qsPosY = temp[1];
241 
242         int xDiff = qsPosX - qqsPosX - xOffset;
243         animatorBuilderX.addFloat(qqsView, "translationX", 0, xDiff);
244         animatorBuilderX.addFloat(qsView, "translationX", -xDiff, 0);
245         int yDiff = qsPosY - qqsPosY - yOffset;
246         animatorBuilderY.addFloat(qqsView, "translationY", 0, yDiff);
247         animatorBuilderY.addFloat(qsView, "translationY", -yDiff, 0);
248         mAllViews.add(qqsView);
249         mAllViews.add(qsView);
250     }
251 
updateAnimators()252     private void updateAnimators() {
253         mNeedsAnimatorUpdate = false;
254         TouchAnimator.Builder firstPageBuilder = new Builder();
255         TouchAnimator.Builder translationYBuilder = new Builder();
256         TouchAnimator.Builder translationXBuilder = new Builder();
257 
258         Collection<QSTile> tiles = mHost.getTiles();
259         int count = 0;
260         int[] loc1 = new int[2];
261         int[] loc2 = new int[2];
262 
263         clearAnimationState();
264         mAllViews.clear();
265         mQuickQsViews.clear();
266         mQQSTileHeightAnimator = null;
267         mOtherTilesExpandAnimator = null;
268 
269         mNumQuickTiles = mQuickQsPanel.getNumQuickTiles();
270 
271         QSTileLayout tileLayout = mQsPanelController.getTileLayout();
272         mAllViews.add((View) tileLayout);
273         int height = mQs.getView() != null ? mQs.getView().getMeasuredHeight() : 0;
274         int heightDiff = height - mQs.getHeader().getBottom()
275                 + mQs.getHeader().getPaddingBottom();
276         if (!mTranslateWhileExpanding) {
277             heightDiff *= SHORT_PARALLAX_AMOUNT;
278         }
279         firstPageBuilder.addFloat(tileLayout, "translationY", heightDiff, 0);
280 
281         int qqsTileHeight = 0;
282 
283         if (mQsPanelController.areThereTiles()) {
284             for (QSTile tile : tiles) {
285                 QSTileView tileView = mQsPanelController.getTileView(tile);
286                 if (tileView == null) {
287                     Log.e(TAG, "tileView is null " + tile.getTileSpec());
288                     continue;
289                 }
290                 final View tileIcon = tileView.getIcon().getIconView();
291                 View view = mQs.getView();
292 
293                 // This case: less tiles to animate in small displays.
294                 if (count < mQuickQSPanelController.getTileLayout().getNumVisibleTiles()
295                         && mAllowFancy) {
296                     // Quick tiles.
297                     QSTileView quickTileView = mQuickQSPanelController.getTileView(tile);
298                     if (quickTileView == null) continue;
299 
300                     getRelativePosition(loc1, quickTileView, view);
301                     getRelativePosition(loc2, tileView, view);
302                     int yOffset = loc2[1] - loc1[1];
303                     int xOffset = loc2[0] - loc1[0];
304 
305                     // Offset the translation animation on the views
306                     // (that goes from 0 to getOffsetTranslation)
307                     int offsetWithQSBHTranslation =
308                             yOffset - mQuickStatusBarHeader.getOffsetTranslation();
309                     translationYBuilder.addFloat(quickTileView, "translationY", 0,
310                             offsetWithQSBHTranslation);
311                     translationYBuilder.addFloat(tileView, "translationY",
312                             -offsetWithQSBHTranslation, 0);
313 
314                     translationXBuilder.addFloat(quickTileView, "translationX", 0, xOffset);
315                     translationXBuilder.addFloat(tileView, "translationX", -xOffset, 0);
316 
317                     if (mQQSTileHeightAnimator == null) {
318                         mQQSTileHeightAnimator = new HeightExpansionAnimator(this,
319                                 quickTileView.getHeight(), tileView.getHeight());
320                         qqsTileHeight = quickTileView.getHeight();
321                     }
322 
323                     mQQSTileHeightAnimator.addView(quickTileView);
324 
325                     // Icons
326                     translateContent(
327                             quickTileView.getIcon(),
328                             tileView.getIcon(),
329                             view,
330                             xOffset,
331                             yOffset,
332                             loc1,
333                             translationXBuilder,
334                             translationYBuilder
335                     );
336 
337                     // Label containers
338                     translateContent(
339                             quickTileView.getLabelContainer(),
340                             tileView.getLabelContainer(),
341                             view,
342                             xOffset,
343                             yOffset,
344                             loc1,
345                             translationXBuilder,
346                             translationYBuilder
347                     );
348 
349                     // Secondary icon
350                     translateContent(
351                             quickTileView.getSecondaryIcon(),
352                             tileView.getSecondaryIcon(),
353                             view,
354                             xOffset,
355                             yOffset,
356                             loc1,
357                             translationXBuilder,
358                             translationYBuilder
359                     );
360 
361                     firstPageBuilder.addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 1);
362 
363                     mQuickQsViews.add(tileView);
364                     mAllViews.add(quickTileView);
365                     mAllViews.add(quickTileView.getSecondaryLabel());
366                 } else if (mFullRows && isIconInAnimatedRow(count)) {
367 
368                     firstPageBuilder.addFloat(tileView, "translationY", -heightDiff, 0);
369 
370                     mAllViews.add(tileIcon);
371                 } else {
372                     // Pretend there's a corresponding QQS tile (for the position) that we are
373                     // expanding from.
374                     SideLabelTileLayout qqsLayout =
375                             (SideLabelTileLayout) mQuickQsPanel.getTileLayout();
376                     getRelativePosition(loc1, qqsLayout, view);
377                     getRelativePosition(loc2, tileView, view);
378                     int diff = loc2[1] - (loc1[1] + qqsLayout.getPhantomTopPosition(count));
379                     translationYBuilder.addFloat(tileView, "translationY", -diff, 0);
380                     if (mOtherTilesExpandAnimator == null) {
381                         mOtherTilesExpandAnimator =
382                                 new HeightExpansionAnimator(
383                                         this, qqsTileHeight, tileView.getHeight());
384                     }
385                     mOtherTilesExpandAnimator.addView(tileView);
386                     tileView.setClipChildren(true);
387                     tileView.setClipToPadding(true);
388                     firstPageBuilder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 1);
389                 }
390 
391                 mAllViews.add(tileView);
392                 count++;
393             }
394         }
395 
396         if (mAllowFancy) {
397             // Make brightness appear static position and alpha in through second half.
398             View brightness = mQsPanelController.getBrightnessView();
399             if (brightness != null) {
400                 firstPageBuilder.addFloat(brightness, "translationY",
401                         brightness.getMeasuredHeight() * 0.5f, 0);
402                 mBrightnessAnimator = new TouchAnimator.Builder()
403                         .addFloat(brightness, "alpha", 0, 1)
404                         .addFloat(brightness, "sliderScaleY", 0.3f, 1)
405                         .setInterpolator(Interpolators.ALPHA_IN)
406                         .setStartDelay(0.3f)
407                         .build();
408                 mAllViews.add(brightness);
409             } else {
410                 mBrightnessAnimator = null;
411             }
412             mFirstPageAnimator = firstPageBuilder
413                     .setListener(this)
414                     .build();
415             // Fade in the tiles/labels as we reach the final position.
416             Builder builder = new Builder()
417                     .addFloat(tileLayout, "alpha", 0, 1);
418             mFirstPageDelayedAnimator = builder.build();
419 
420             // Fade in the security footer and the divider as we reach the final position
421             builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY);
422             builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1);
423             if (mQsPanelController.shouldUseHorizontalLayout()
424                     && mQsPanelController.mMediaHost.hostView != null) {
425                 builder.addFloat(mQsPanelController.mMediaHost.hostView, "alpha", 0, 1);
426             } else {
427                 // In portrait, media view should always be visible
428                 mQsPanelController.mMediaHost.hostView.setAlpha(1.0f);
429             }
430             mAllPagesDelayedAnimator = builder.build();
431             mAllViews.add(mSecurityFooter.getView());
432             translationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
433             translationXBuilder.setInterpolator(mQSExpansionPathInterpolator.getXInterpolator());
434             mTranslationYAnimator = translationYBuilder.build();
435             mTranslationXAnimator = translationXBuilder.build();
436             if (mQQSTileHeightAnimator != null) {
437                 mQQSTileHeightAnimator.setInterpolator(
438                         mQSExpansionPathInterpolator.getYInterpolator());
439             }
440             if (mOtherTilesExpandAnimator != null) {
441                 mOtherTilesExpandAnimator.setInterpolator(
442                         mQSExpansionPathInterpolator.getYInterpolator());
443             }
444         }
445         mNonfirstPageAnimator = new TouchAnimator.Builder()
446                 .addFloat(mQuickQsPanel, "alpha", 1, 0)
447                 .setListener(mNonFirstPageListener)
448                 .setEndDelay(.5f)
449                 .build();
450         mNonfirstPageDelayedAnimator = new TouchAnimator.Builder()
451                 .setStartDelay(.14f)
452                 .addFloat(tileLayout, "alpha", 0, 1).build();
453     }
454 
isIconInAnimatedRow(int count)455     private boolean isIconInAnimatedRow(int count) {
456         if (mPagedLayout == null) {
457             return false;
458         }
459         final int columnCount = mPagedLayout.getColumnCount();
460         return count < ((mNumQuickTiles + columnCount - 1) / columnCount) * columnCount;
461     }
462 
getRelativePosition(int[] loc1, View view, View parent)463     private void getRelativePosition(int[] loc1, View view, View parent) {
464         loc1[0] = 0 + view.getWidth() / 2;
465         loc1[1] = 0;
466         getRelativePositionInt(loc1, view, parent);
467     }
468 
getRelativePositionInt(int[] loc1, View view, View parent)469     private void getRelativePositionInt(int[] loc1, View view, View parent) {
470         if(view == parent || view == null) return;
471         // Ignore tile pages as they can have some offset we don't want to take into account in
472         // RTL.
473         if (!isAPage(view)) {
474             loc1[0] += view.getLeft();
475             loc1[1] += view.getTop();
476         }
477         if (!(view instanceof PagedTileLayout)) {
478             // Remove the scrolling position of all scroll views other than the viewpager
479             loc1[0] -= view.getScrollX();
480             loc1[1] -= view.getScrollY();
481         }
482         getRelativePositionInt(loc1, (View) view.getParent(), parent);
483     }
484 
485     // Returns true if the view is a possible page in PagedTileLayout
isAPage(View view)486     private boolean isAPage(View view) {
487         return view.getClass().equals(SideLabelTileLayout.class);
488     }
489 
setPosition(float position)490     public void setPosition(float position) {
491         if (mNeedsAnimatorUpdate) {
492             updateAnimators();
493         }
494         if (mFirstPageAnimator == null) return;
495         if (mOnKeyguard) {
496             if (mShowCollapsedOnKeyguard) {
497                 position = 0;
498             } else {
499                 position = 1;
500             }
501         }
502         mLastPosition = position;
503         if (mOnFirstPage && mAllowFancy) {
504             mQuickQsPanel.setAlpha(1);
505             mFirstPageAnimator.setPosition(position);
506             mFirstPageDelayedAnimator.setPosition(position);
507             mTranslationYAnimator.setPosition(position);
508             mTranslationXAnimator.setPosition(position);
509             if (mQQSTileHeightAnimator != null) {
510                 mQQSTileHeightAnimator.setPosition(position);
511             }
512             if (mOtherTilesExpandAnimator != null) {
513                 mOtherTilesExpandAnimator.setPosition(position);
514             }
515         } else {
516             mNonfirstPageAnimator.setPosition(position);
517             mNonfirstPageDelayedAnimator.setPosition(position);
518         }
519         if (mAllowFancy) {
520             mAllPagesDelayedAnimator.setPosition(position);
521             if (mBrightnessAnimator != null) {
522                 mBrightnessAnimator.setPosition(position);
523             }
524         }
525     }
526 
527     @Override
onAnimationAtStart()528     public void onAnimationAtStart() {
529         mQuickQsPanel.setVisibility(View.VISIBLE);
530     }
531 
532     @Override
onAnimationAtEnd()533     public void onAnimationAtEnd() {
534         mQuickQsPanel.setVisibility(View.INVISIBLE);
535         final int N = mQuickQsViews.size();
536         for (int i = 0; i < N; i++) {
537             mQuickQsViews.get(i).setVisibility(View.VISIBLE);
538         }
539     }
540 
541     @Override
onAnimationStarted()542     public void onAnimationStarted() {
543         updateQQSVisibility();
544         if (mOnFirstPage) {
545             final int N = mQuickQsViews.size();
546             for (int i = 0; i < N; i++) {
547                 mQuickQsViews.get(i).setVisibility(View.INVISIBLE);
548             }
549         }
550     }
551 
clearAnimationState()552     private void clearAnimationState() {
553         final int N = mAllViews.size();
554         mQuickQsPanel.setAlpha(0);
555         for (int i = 0; i < N; i++) {
556             View v = mAllViews.get(i);
557             v.setAlpha(1);
558             v.setTranslationX(0);
559             v.setTranslationY(0);
560             v.setScaleY(1f);
561             if (v instanceof SideLabelTileLayout) {
562                 ((SideLabelTileLayout) v).setClipChildren(false);
563                 ((SideLabelTileLayout) v).setClipToPadding(false);
564             }
565         }
566         if (mQQSTileHeightAnimator != null) {
567             mQQSTileHeightAnimator.resetViewsHeights();
568         }
569         if (mOtherTilesExpandAnimator != null) {
570             mOtherTilesExpandAnimator.resetViewsHeights();
571         }
572         final int N2 = mQuickQsViews.size();
573         for (int i = 0; i < N2; i++) {
574             mQuickQsViews.get(i).setVisibility(View.VISIBLE);
575         }
576     }
577 
578     @Override
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)579     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
580             int oldTop, int oldRight, int oldBottom) {
581         mExecutor.execute(mUpdateAnimators);
582     }
583 
584     @Override
onTilesChanged()585     public void onTilesChanged() {
586         // Give the QS panels a moment to generate their new tiles, then create all new animators
587         // hooked up to the new views.
588         mExecutor.execute(mUpdateAnimators);
589     }
590 
591     private final TouchAnimator.Listener mNonFirstPageListener =
592             new TouchAnimator.ListenerAdapter() {
593                 @Override
594                 public void onAnimationAtEnd() {
595                     mQuickQsPanel.setVisibility(View.INVISIBLE);
596                 }
597 
598                 @Override
599                 public void onAnimationStarted() {
600                     mQuickQsPanel.setVisibility(View.VISIBLE);
601                 }
602             };
603 
604     private final Runnable mUpdateAnimators = () -> {
605         updateAnimators();
606         setCurrentPosition();
607     };
608 
609     /**
610      * True whe QS will be pulled from the top, false when it will be clipped.
611      */
setTranslateWhileExpanding(boolean shouldTranslate)612     public void setTranslateWhileExpanding(boolean shouldTranslate) {
613         mTranslateWhileExpanding = shouldTranslate;
614     }
615 
616     static class HeightExpansionAnimator {
617         private final List<View> mViews = new ArrayList<>();
618         private final ValueAnimator mAnimator;
619         private final TouchAnimator.Listener mListener;
620 
621         private final ValueAnimator.AnimatorUpdateListener mUpdateListener =
622                 new ValueAnimator.AnimatorUpdateListener() {
623             float mLastT = -1;
624             @Override
625             public void onAnimationUpdate(ValueAnimator valueAnimator) {
626                 float t = valueAnimator.getAnimatedFraction();
627                 final int viewCount = mViews.size();
628                 int height = (Integer) valueAnimator.getAnimatedValue();
629                 for (int i = 0; i < viewCount; i++) {
630                     View v = mViews.get(i);
631                     v.setBottom(v.getTop() + height);
632                     if (v instanceof HeightOverrideable) {
633                         ((HeightOverrideable) v).setHeightOverride(height);
634                     }
635                 }
636                 if (t == 0f) {
637                     mListener.onAnimationAtStart();
638                 } else if (t == 1f) {
639                     mListener.onAnimationAtEnd();
640                 } else if (mLastT <= 0 || mLastT == 1) {
641                     mListener.onAnimationStarted();
642                 }
643                 mLastT = t;
644             }
645         };
646 
HeightExpansionAnimator(TouchAnimator.Listener listener, int startHeight, int endHeight)647         HeightExpansionAnimator(TouchAnimator.Listener listener, int startHeight, int endHeight) {
648             mListener = listener;
649             mAnimator = ValueAnimator.ofInt(startHeight, endHeight);
650             mAnimator.setRepeatCount(ValueAnimator.INFINITE);
651             mAnimator.setRepeatMode(ValueAnimator.REVERSE);
652             mAnimator.addUpdateListener(mUpdateListener);
653         }
654 
addView(View v)655         void addView(View v) {
656             mViews.add(v);
657         }
658 
setInterpolator(TimeInterpolator interpolator)659         void setInterpolator(TimeInterpolator interpolator) {
660             mAnimator.setInterpolator(interpolator);
661         }
662 
setPosition(float position)663         void setPosition(float position) {
664             mAnimator.setCurrentFraction(position);
665         }
666 
resetViewsHeights()667         void resetViewsHeights() {
668             final int viewsCount = mViews.size();
669             for (int i = 0; i < viewsCount; i++) {
670                 View v = mViews.get(i);
671                 v.setBottom(v.getTop() + v.getMeasuredHeight());
672                 if (v instanceof HeightOverrideable) {
673                     ((HeightOverrideable) v).resetOverride();
674                 }
675             }
676         }
677     }
678 }
679