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 static com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER;
18 import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FOOTER;
19 
20 import android.animation.TimeInterpolator;
21 import android.animation.ValueAnimator;
22 import android.util.Log;
23 import android.view.View;
24 import android.view.View.OnAttachStateChangeListener;
25 import android.view.View.OnLayoutChangeListener;
26 
27 import com.android.systemui.dagger.qualifiers.Main;
28 import com.android.systemui.plugins.qs.QS;
29 import com.android.systemui.plugins.qs.QSTile;
30 import com.android.systemui.plugins.qs.QSTileView;
31 import com.android.systemui.qs.PagedTileLayout.PageListener;
32 import com.android.systemui.qs.QSHost.Callback;
33 import com.android.systemui.qs.QSPanel.QSTileLayout;
34 import com.android.systemui.qs.TouchAnimator.Builder;
35 import com.android.systemui.qs.TouchAnimator.Listener;
36 import com.android.systemui.qs.dagger.QSScope;
37 import com.android.systemui.qs.tileimpl.HeightOverrideable;
38 import com.android.systemui.tuner.TunerService;
39 import com.android.systemui.tuner.TunerService.Tunable;
40 import com.android.wm.shell.animation.Interpolators;
41 
42 import java.util.ArrayList;
43 import java.util.Collection;
44 import java.util.List;
45 import java.util.concurrent.Executor;
46 
47 import javax.inject.Inject;
48 import javax.inject.Named;
49 
50 /** */
51 @QSScope
52 public class QSAnimator implements Callback, PageListener, Listener, OnLayoutChangeListener,
53         OnAttachStateChangeListener, Tunable {
54 
55     private static final String TAG = "QSAnimator";
56 
57     private static final String ALLOW_FANCY_ANIMATION = "sysui_qs_fancy_anim";
58     private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows";
59 
60     public static final float EXPANDED_TILE_DELAY = .86f;
61     public static final float SHORT_PARALLAX_AMOUNT = 0.1f;
62     private static final long QQS_FADE_IN_DURATION = 200L;
63     // Fade out faster than fade in to finish before QQS hides.
64     private static final long QQS_FADE_OUT_DURATION = 50L;
65 
66     /**
67      * List of all views that will be reset when clearing animation state
68      * see {@link #clearAnimationState()} }
69      */
70     private final ArrayList<View> mAllViews = new ArrayList<>();
71     /**
72      * List of {@link View}s representing Quick Settings that are being animated from the quick QS
73      * position to the normal QS panel. These views will only show once the animation is complete,
74      * to prevent overlapping of semi transparent views
75      */
76     private final ArrayList<View> mAnimatedQsViews = new ArrayList<>();
77     private final QuickQSPanel mQuickQsPanel;
78     private final QSPanelController mQsPanelController;
79     private final QuickQSPanelController mQuickQSPanelController;
80     private final QuickStatusBarHeader mQuickStatusBarHeader;
81     private final QSSecurityFooter mSecurityFooter;
82     private final QS mQs;
83     private final View mQSFooterActions;
84     private final View mQQSFooterActions;
85 
86     private PagedTileLayout mPagedLayout;
87 
88     private boolean mOnFirstPage = true;
89     private QSExpansionPathInterpolator mQSExpansionPathInterpolator;
90     private TouchAnimator mFirstPageAnimator;
91     private TouchAnimator mFirstPageDelayedAnimator;
92     private TouchAnimator mTranslationXAnimator;
93     private TouchAnimator mTranslationYAnimator;
94     private TouchAnimator mNonfirstPageAnimator;
95     private TouchAnimator mNonfirstPageDelayedAnimator;
96     // This animates fading of SecurityFooter and media divider
97     private TouchAnimator mAllPagesDelayedAnimator;
98     private TouchAnimator mBrightnessAnimator;
99     private TouchAnimator mQQSFooterActionsAnimator;
100     private HeightExpansionAnimator mQQSTileHeightAnimator;
101     private HeightExpansionAnimator mOtherTilesExpandAnimator;
102 
103     private boolean mNeedsAnimatorUpdate = false;
104     private boolean mToShowing;
105     private boolean mOnKeyguard;
106 
107     private boolean mAllowFancy;
108     private boolean mFullRows;
109     private int mNumQuickTiles;
110     private float mLastPosition;
111     private final QSTileHost mHost;
112     private final Executor mExecutor;
113     private final TunerService mTunerService;
114     private boolean mShowCollapsedOnKeyguard;
115     private boolean mTranslateWhileExpanding;
116 
117     @Inject
QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader, QSPanelController qsPanelController, QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost, QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService, QSExpansionPathInterpolator qsExpansionPathInterpolator, @Named(QS_FOOTER) FooterActionsView qsFooterActionsView, @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView)118     public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
119             QSPanelController qsPanelController,
120             QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
121             QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService,
122             QSExpansionPathInterpolator qsExpansionPathInterpolator,
123             @Named(QS_FOOTER) FooterActionsView qsFooterActionsView,
124             @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView) {
125         mQs = qs;
126         mQuickQsPanel = quickPanel;
127         mQsPanelController = qsPanelController;
128         mQuickQSPanelController = quickQSPanelController;
129         mQuickStatusBarHeader = quickStatusBarHeader;
130         mQQSFooterActions = qqsFooterActionsView;
131         mQSFooterActions = qsFooterActionsView;
132         mSecurityFooter = securityFooter;
133         mHost = qsTileHost;
134         mExecutor = executor;
135         mTunerService = tunerService;
136         mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
137         mHost.addCallback(this);
138         mQsPanelController.addOnAttachStateChangeListener(this);
139         qs.getView().addOnLayoutChangeListener(this);
140         if (mQsPanelController.isAttachedToWindow()) {
141             onViewAttachedToWindow(null);
142         }
143         QSTileLayout tileLayout = mQsPanelController.getTileLayout();
144         if (tileLayout instanceof PagedTileLayout) {
145             mPagedLayout = ((PagedTileLayout) tileLayout);
146         } else {
147             Log.w(TAG, "QS Not using page layout");
148         }
149         mQsPanelController.setPageListener(this);
150     }
151 
onRtlChanged()152     public void onRtlChanged() {
153         updateAnimators();
154     }
155 
156     /**
157      * Request an update to the animators. This will update them lazily next time the position
158      * is changed.
159      */
requestAnimatorUpdate()160     public void requestAnimatorUpdate() {
161         mNeedsAnimatorUpdate = true;
162     }
163 
setOnKeyguard(boolean onKeyguard)164     public void setOnKeyguard(boolean onKeyguard) {
165         mOnKeyguard = onKeyguard;
166         updateQQSVisibility();
167         if (mOnKeyguard) {
168             clearAnimationState();
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         mAnimatedQsViews.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.getMeasuredHeight(), tileView.getMeasuredHeight());
320                         qqsTileHeight = quickTileView.getMeasuredHeight();
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                     mAnimatedQsViews.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.getMeasuredHeight());
384                     }
385                     mOtherTilesExpandAnimator.addView(tileView);
386                     tileView.setClipChildren(true);
387                     tileView.setClipToPadding(true);
388                     firstPageBuilder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 1);
389                     mAllViews.add(tileView.getSecondaryLabel());
390                 }
391 
392                 mAllViews.add(tileView);
393                 count++;
394             }
395         }
396 
397         if (mAllowFancy) {
398             animateBrightnessSlider(firstPageBuilder);
399 
400             mFirstPageAnimator = firstPageBuilder
401                     .setListener(this)
402                     .build();
403             // Fade in the tiles/labels as we reach the final position.
404             Builder builder = new Builder()
405                     .addFloat(tileLayout, "alpha", 0, 1);
406             mFirstPageDelayedAnimator = builder.build();
407 
408             if (mQQSFooterActions.getVisibility() != View.GONE) {
409                 // only when qqs footer is present (which means split shade mode) it needs to
410                 // be animated
411                 updateQQSFooterAnimation();
412             }
413 
414 
415             // Fade in the security footer and the divider as we reach the final position
416             builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY);
417             builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1);
418             if (mQsPanelController.shouldUseHorizontalLayout()
419                     && mQsPanelController.mMediaHost.hostView != null) {
420                 builder.addFloat(mQsPanelController.mMediaHost.hostView, "alpha", 0, 1);
421             } else {
422                 // In portrait, media view should always be visible
423                 mQsPanelController.mMediaHost.hostView.setAlpha(1.0f);
424             }
425             mAllPagesDelayedAnimator = builder.build();
426             mAllViews.add(mSecurityFooter.getView());
427             translationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
428             translationXBuilder.setInterpolator(mQSExpansionPathInterpolator.getXInterpolator());
429             mTranslationYAnimator = translationYBuilder.build();
430             mTranslationXAnimator = translationXBuilder.build();
431             if (mQQSTileHeightAnimator != null) {
432                 mQQSTileHeightAnimator.setInterpolator(
433                         mQSExpansionPathInterpolator.getYInterpolator());
434             }
435             if (mOtherTilesExpandAnimator != null) {
436                 mOtherTilesExpandAnimator.setInterpolator(
437                         mQSExpansionPathInterpolator.getYInterpolator());
438             }
439         }
440         mNonfirstPageAnimator = new TouchAnimator.Builder()
441                 .addFloat(mQuickQsPanel, "alpha", 1, 0)
442                 .setListener(mNonFirstPageListener)
443                 .setEndDelay(.5f)
444                 .build();
445         mNonfirstPageDelayedAnimator = new TouchAnimator.Builder()
446                 .setStartDelay(.14f)
447                 .addFloat(tileLayout, "alpha", 0, 1).build();
448     }
449 
animateBrightnessSlider(Builder firstPageBuilder)450     private void animateBrightnessSlider(Builder firstPageBuilder) {
451         View qsBrightness = mQsPanelController.getBrightnessView();
452         View qqsBrightness = mQuickQSPanelController.getBrightnessView();
453         if (qqsBrightness != null && qqsBrightness.getVisibility() == View.VISIBLE) {
454             // animating in split shade mode
455             mAnimatedQsViews.add(qsBrightness);
456             mAllViews.add(qqsBrightness);
457             int translationY = getRelativeTranslationY(qsBrightness, qqsBrightness);
458             mBrightnessAnimator = new Builder()
459                     // we need to animate qs brightness even if animation will not be visible,
460                     // as we might start from sliderScaleY set to 0.3 if device was in collapsed QS
461                     // portrait orientation before
462                     .addFloat(qsBrightness, "sliderScaleY", 0.3f, 1)
463                     .addFloat(qqsBrightness, "translationY", 0, translationY)
464                     .build();
465         } else if (qsBrightness != null) {
466             firstPageBuilder.addFloat(qsBrightness, "translationY",
467                     qsBrightness.getMeasuredHeight() * 0.5f, 0);
468             mBrightnessAnimator = new Builder()
469                     .addFloat(qsBrightness, "alpha", 0, 1)
470                     .addFloat(qsBrightness, "sliderScaleY", 0.3f, 1)
471                     .setInterpolator(Interpolators.ALPHA_IN)
472                     .setStartDelay(0.3f)
473                     .build();
474             mAllViews.add(qsBrightness);
475         } else {
476             mBrightnessAnimator = null;
477         }
478     }
479 
updateQQSFooterAnimation()480     private void updateQQSFooterAnimation() {
481         int translationY = getRelativeTranslationY(mQSFooterActions, mQQSFooterActions);
482         mQQSFooterActionsAnimator = new TouchAnimator.Builder()
483                 .addFloat(mQQSFooterActions, "translationY", 0, translationY)
484                 .build();
485         mAnimatedQsViews.add(mQSFooterActions);
486     }
487 
getRelativeTranslationY(View view1, View view2)488     private int getRelativeTranslationY(View view1, View view2) {
489         int[] qsPosition = new int[2];
490         int[] qqsPosition = new int[2];
491         View commonView = mQs.getView();
492         getRelativePositionInt(qsPosition, view1, commonView);
493         getRelativePositionInt(qqsPosition, view2, commonView);
494         return (qsPosition[1] - qqsPosition[1]) - mQuickStatusBarHeader.getOffsetTranslation();
495     }
496 
isIconInAnimatedRow(int count)497     private boolean isIconInAnimatedRow(int count) {
498         if (mPagedLayout == null) {
499             return false;
500         }
501         final int columnCount = mPagedLayout.getColumnCount();
502         return count < ((mNumQuickTiles + columnCount - 1) / columnCount) * columnCount;
503     }
504 
getRelativePosition(int[] loc1, View view, View parent)505     private void getRelativePosition(int[] loc1, View view, View parent) {
506         loc1[0] = 0 + view.getWidth() / 2;
507         loc1[1] = 0;
508         getRelativePositionInt(loc1, view, parent);
509     }
510 
getRelativePositionInt(int[] loc1, View view, View parent)511     private void getRelativePositionInt(int[] loc1, View view, View parent) {
512         if(view == parent || view == null) return;
513         // Ignore tile pages as they can have some offset we don't want to take into account in
514         // RTL.
515         if (!isAPage(view)) {
516             loc1[0] += view.getLeft();
517             loc1[1] += view.getTop();
518         }
519         if (!(view instanceof PagedTileLayout)) {
520             // Remove the scrolling position of all scroll views other than the viewpager
521             loc1[0] -= view.getScrollX();
522             loc1[1] -= view.getScrollY();
523         }
524         getRelativePositionInt(loc1, (View) view.getParent(), parent);
525     }
526 
527     // Returns true if the view is a possible page in PagedTileLayout
isAPage(View view)528     private boolean isAPage(View view) {
529         return view.getClass().equals(SideLabelTileLayout.class);
530     }
531 
setPosition(float position)532     public void setPosition(float position) {
533         if (mNeedsAnimatorUpdate) {
534             updateAnimators();
535         }
536         if (mFirstPageAnimator == null) return;
537         if (mOnKeyguard) {
538             if (mShowCollapsedOnKeyguard) {
539                 position = 0;
540             } else {
541                 position = 1;
542             }
543         }
544         mLastPosition = position;
545         if (mOnFirstPage && mAllowFancy) {
546             mQuickQsPanel.setAlpha(1);
547             mFirstPageAnimator.setPosition(position);
548             mFirstPageDelayedAnimator.setPosition(position);
549             mTranslationYAnimator.setPosition(position);
550             mTranslationXAnimator.setPosition(position);
551             if (mQQSTileHeightAnimator != null) {
552                 mQQSTileHeightAnimator.setPosition(position);
553             }
554             if (mOtherTilesExpandAnimator != null) {
555                 mOtherTilesExpandAnimator.setPosition(position);
556             }
557         } else {
558             mNonfirstPageAnimator.setPosition(position);
559             mNonfirstPageDelayedAnimator.setPosition(position);
560         }
561         if (mAllowFancy) {
562             mAllPagesDelayedAnimator.setPosition(position);
563             if (mBrightnessAnimator != null) {
564                 mBrightnessAnimator.setPosition(position);
565             }
566             if (mQQSFooterActionsAnimator != null) {
567                 mQQSFooterActionsAnimator.setPosition(position);
568             }
569         }
570     }
571 
572     @Override
onAnimationAtStart()573     public void onAnimationAtStart() {
574         mQuickQsPanel.setVisibility(View.VISIBLE);
575     }
576 
577     @Override
onAnimationAtEnd()578     public void onAnimationAtEnd() {
579         mQuickQsPanel.setVisibility(View.INVISIBLE);
580         final int N = mAnimatedQsViews.size();
581         for (int i = 0; i < N; i++) {
582             mAnimatedQsViews.get(i).setVisibility(View.VISIBLE);
583         }
584     }
585 
586     @Override
onAnimationStarted()587     public void onAnimationStarted() {
588         updateQQSVisibility();
589         if (mOnFirstPage) {
590             final int N = mAnimatedQsViews.size();
591             for (int i = 0; i < N; i++) {
592                 mAnimatedQsViews.get(i).setVisibility(View.INVISIBLE);
593             }
594         }
595     }
596 
clearAnimationState()597     private void clearAnimationState() {
598         final int N = mAllViews.size();
599         mQuickQsPanel.setAlpha(0);
600         for (int i = 0; i < N; i++) {
601             View v = mAllViews.get(i);
602             v.setAlpha(1);
603             v.setTranslationX(0);
604             v.setTranslationY(0);
605             v.setScaleY(1f);
606             if (v instanceof SideLabelTileLayout) {
607                 ((SideLabelTileLayout) v).setClipChildren(false);
608                 ((SideLabelTileLayout) v).setClipToPadding(false);
609             }
610         }
611         if (mQQSTileHeightAnimator != null) {
612             mQQSTileHeightAnimator.resetViewsHeights();
613         }
614         if (mOtherTilesExpandAnimator != null) {
615             mOtherTilesExpandAnimator.resetViewsHeights();
616         }
617         final int N2 = mAnimatedQsViews.size();
618         for (int i = 0; i < N2; i++) {
619             mAnimatedQsViews.get(i).setVisibility(View.VISIBLE);
620         }
621     }
622 
623     @Override
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)624     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
625             int oldTop, int oldRight, int oldBottom) {
626         mExecutor.execute(mUpdateAnimators);
627     }
628 
629     @Override
onTilesChanged()630     public void onTilesChanged() {
631         // Give the QS panels a moment to generate their new tiles, then create all new animators
632         // hooked up to the new views.
633         mExecutor.execute(mUpdateAnimators);
634     }
635 
636     private final TouchAnimator.Listener mNonFirstPageListener =
637             new TouchAnimator.ListenerAdapter() {
638                 @Override
639                 public void onAnimationAtEnd() {
640                     mQuickQsPanel.setVisibility(View.INVISIBLE);
641                 }
642 
643                 @Override
644                 public void onAnimationStarted() {
645                     mQuickQsPanel.setVisibility(View.VISIBLE);
646                 }
647             };
648 
649     private final Runnable mUpdateAnimators = () -> {
650         updateAnimators();
651         setCurrentPosition();
652     };
653 
654     /**
655      * True whe QS will be pulled from the top, false when it will be clipped.
656      */
setTranslateWhileExpanding(boolean shouldTranslate)657     public void setTranslateWhileExpanding(boolean shouldTranslate) {
658         mTranslateWhileExpanding = shouldTranslate;
659     }
660 
661     private static class HeightExpansionAnimator {
662         private final List<View> mViews = new ArrayList<>();
663         private final ValueAnimator mAnimator;
664         private final TouchAnimator.Listener mListener;
665 
666         private final ValueAnimator.AnimatorUpdateListener mUpdateListener =
667                 new ValueAnimator.AnimatorUpdateListener() {
668             float mLastT = -1;
669             @Override
670             public void onAnimationUpdate(ValueAnimator valueAnimator) {
671                 float t = valueAnimator.getAnimatedFraction();
672                 final int viewCount = mViews.size();
673                 int height = (Integer) valueAnimator.getAnimatedValue();
674                 for (int i = 0; i < viewCount; i++) {
675                     View v = mViews.get(i);
676                     if (v instanceof HeightOverrideable) {
677                         ((HeightOverrideable) v).setHeightOverride(height);
678                     } else {
679                         v.setBottom(v.getTop() + height);
680                     }
681                 }
682                 if (t == 0f) {
683                     mListener.onAnimationAtStart();
684                 } else if (t == 1f) {
685                     mListener.onAnimationAtEnd();
686                 } else if (mLastT <= 0 || mLastT == 1) {
687                     mListener.onAnimationStarted();
688                 }
689                 mLastT = t;
690             }
691         };
692 
HeightExpansionAnimator(TouchAnimator.Listener listener, int startHeight, int endHeight)693         HeightExpansionAnimator(TouchAnimator.Listener listener, int startHeight, int endHeight) {
694             mListener = listener;
695             mAnimator = ValueAnimator.ofInt(startHeight, endHeight);
696             mAnimator.setRepeatCount(ValueAnimator.INFINITE);
697             mAnimator.setRepeatMode(ValueAnimator.REVERSE);
698             mAnimator.addUpdateListener(mUpdateListener);
699         }
700 
addView(View v)701         void addView(View v) {
702             mViews.add(v);
703         }
704 
setInterpolator(TimeInterpolator interpolator)705         void setInterpolator(TimeInterpolator interpolator) {
706             mAnimator.setInterpolator(interpolator);
707         }
708 
setPosition(float position)709         void setPosition(float position) {
710             mAnimator.setCurrentFraction(position);
711         }
712 
resetViewsHeights()713         void resetViewsHeights() {
714             final int viewsCount = mViews.size();
715             for (int i = 0; i < viewsCount; i++) {
716                 View v = mViews.get(i);
717                 if (v instanceof HeightOverrideable) {
718                     ((HeightOverrideable) v).resetOverride();
719                 } else {
720                     v.setBottom(v.getTop() + v.getMeasuredHeight());
721                 }
722             }
723         }
724     }
725 }
726