• 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 static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.content.res.Configuration;
22 import android.graphics.Rect;
23 import android.os.Bundle;
24 import android.util.Log;
25 import android.view.ContextThemeWrapper;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.View.OnClickListener;
29 import android.view.ViewGroup;
30 import android.view.ViewTreeObserver;
31 import android.widget.FrameLayout.LayoutParams;
32 
33 import androidx.annotation.Nullable;
34 import androidx.annotation.VisibleForTesting;
35 
36 import com.android.systemui.Interpolators;
37 import com.android.systemui.R;
38 import com.android.systemui.R.id;
39 import com.android.systemui.media.MediaHost;
40 import com.android.systemui.plugins.qs.QS;
41 import com.android.systemui.plugins.statusbar.StatusBarStateController;
42 import com.android.systemui.qs.customize.QSCustomizer;
43 import com.android.systemui.statusbar.CommandQueue;
44 import com.android.systemui.statusbar.StatusBarState;
45 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
46 import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
47 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
48 import com.android.systemui.util.InjectionInflationController;
49 import com.android.systemui.util.LifecycleFragment;
50 import com.android.systemui.util.Utils;
51 
52 import javax.inject.Inject;
53 
54 public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Callbacks,
55         StatusBarStateController.StateListener {
56     private static final String TAG = "QS";
57     private static final boolean DEBUG = false;
58     private static final String EXTRA_EXPANDED = "expanded";
59     private static final String EXTRA_LISTENING = "listening";
60 
61     private final Rect mQsBounds = new Rect();
62     private final StatusBarStateController mStatusBarStateController;
63     private boolean mQsExpanded;
64     private boolean mHeaderAnimating;
65     private boolean mStackScrollerOverscrolling;
66 
67     private long mDelay;
68 
69     private QSAnimator mQSAnimator;
70     private HeightListener mPanelView;
71     protected QuickStatusBarHeader mHeader;
72     private QSCustomizer mQSCustomizer;
73     protected QSPanel mQSPanel;
74     protected NonInterceptingScrollView mQSPanelScrollView;
75     private QSDetail mQSDetail;
76     private boolean mListening;
77     private QSContainerImpl mContainer;
78     private int mLayoutDirection;
79     private QSFooter mFooter;
80     private float mLastQSExpansion = -1;
81     private boolean mQsDisabled;
82 
83     private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
84     private final InjectionInflationController mInjectionInflater;
85     private final QSContainerImplController.Builder mQSContainerImplControllerBuilder;
86     private final QSTileHost mHost;
87     private boolean mShowCollapsedOnKeyguard;
88     private boolean mLastKeyguardAndExpanded;
89     /**
90      * The last received state from the controller. This should not be used directly to check if
91      * we're on keyguard but use {@link #isKeyguardShowing()} instead since that is more accurate
92      * during state transitions which often call into us.
93      */
94     private int mState;
95     private QSContainerImplController mQSContainerImplController;
96     private int[] mTmpLocation = new int[2];
97     private int mLastViewHeight;
98     private float mLastHeaderTranslation;
99 
100     @Inject
QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, InjectionInflationController injectionInflater, QSTileHost qsTileHost, StatusBarStateController statusBarStateController, CommandQueue commandQueue, QSContainerImplController.Builder qsContainerImplControllerBuilder)101     public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
102             InjectionInflationController injectionInflater, QSTileHost qsTileHost,
103             StatusBarStateController statusBarStateController, CommandQueue commandQueue,
104             QSContainerImplController.Builder qsContainerImplControllerBuilder) {
105         mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
106         mInjectionInflater = injectionInflater;
107         mQSContainerImplControllerBuilder = qsContainerImplControllerBuilder;
108         commandQueue.observe(getLifecycle(), this);
109         mHost = qsTileHost;
110         mStatusBarStateController = statusBarStateController;
111     }
112 
113     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState)114     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
115             Bundle savedInstanceState) {
116         inflater = mInjectionInflater.injectable(
117                 inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme)));
118         return inflater.inflate(R.layout.qs_panel, container, false);
119     }
120 
121     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)122     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
123         super.onViewCreated(view, savedInstanceState);
124         mQSPanel = view.findViewById(R.id.quick_settings_panel);
125         mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
126         mQSPanelScrollView.addOnLayoutChangeListener(
127                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
128                     updateQsBounds();
129                 });
130         mQSPanelScrollView.setOnScrollChangeListener(
131                 (v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
132             // Lazily update animators whenever the scrolling changes
133             mQSAnimator.onQsScrollingChanged();
134             mHeader.setExpandedScrollAmount(scrollY);
135         });
136         mQSDetail = view.findViewById(R.id.qs_detail);
137         mHeader = view.findViewById(R.id.header);
138         mQSPanel.setHeaderContainer(view.findViewById(R.id.header_text_container));
139         mFooter = view.findViewById(R.id.qs_footer);
140         mContainer = view.findViewById(id.quick_settings_container);
141 
142         mQSContainerImplController = mQSContainerImplControllerBuilder
143                 .setQSContainerImpl((QSContainerImpl) view)
144                 .build();
145 
146 
147         mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter);
148         mQSAnimator = new QSAnimator(this, mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
149 
150 
151         mQSCustomizer = view.findViewById(R.id.qs_customize);
152         mQSCustomizer.setQs(this);
153         if (savedInstanceState != null) {
154             setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
155             setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
156             setEditLocation(view);
157             mQSCustomizer.restoreInstanceState(savedInstanceState);
158             if (mQsExpanded) {
159                 mQSPanel.getTileLayout().restoreInstanceState(savedInstanceState);
160             }
161         }
162         setHost(mHost);
163         mStatusBarStateController.addCallback(this);
164         onStateChanged(mStatusBarStateController.getState());
165         view.addOnLayoutChangeListener(
166                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
167                     boolean sizeChanged = (oldTop - oldBottom) != (top - bottom);
168                     if (sizeChanged) {
169                         setQsExpansion(mLastQSExpansion, mLastQSExpansion);
170                     }
171                 });
172     }
173 
174     @Override
onDestroy()175     public void onDestroy() {
176         super.onDestroy();
177         mStatusBarStateController.removeCallback(this);
178         if (mListening) {
179             setListening(false);
180         }
181         mQSCustomizer.setQs(null);
182     }
183 
184     @Override
onSaveInstanceState(Bundle outState)185     public void onSaveInstanceState(Bundle outState) {
186         super.onSaveInstanceState(outState);
187         outState.putBoolean(EXTRA_EXPANDED, mQsExpanded);
188         outState.putBoolean(EXTRA_LISTENING, mListening);
189         mQSCustomizer.saveInstanceState(outState);
190         if (mQsExpanded) {
191             mQSPanel.getTileLayout().saveInstanceState(outState);
192         }
193     }
194 
195     @VisibleForTesting
isListening()196     boolean isListening() {
197         return mListening;
198     }
199 
200     @VisibleForTesting
isExpanded()201     boolean isExpanded() {
202         return mQsExpanded;
203     }
204 
205     @Override
getHeader()206     public View getHeader() {
207         return mHeader;
208     }
209 
210     @Override
setHasNotifications(boolean hasNotifications)211     public void setHasNotifications(boolean hasNotifications) {
212     }
213 
214     @Override
setPanelView(HeightListener panelView)215     public void setPanelView(HeightListener panelView) {
216         mPanelView = panelView;
217     }
218 
219     @Override
onConfigurationChanged(Configuration newConfig)220     public void onConfigurationChanged(Configuration newConfig) {
221         super.onConfigurationChanged(newConfig);
222         setEditLocation(getView());
223         if (newConfig.getLayoutDirection() != mLayoutDirection) {
224             mLayoutDirection = newConfig.getLayoutDirection();
225             if (mQSAnimator != null) {
226                 mQSAnimator.onRtlChanged();
227             }
228         }
229     }
230 
setEditLocation(View view)231     private void setEditLocation(View view) {
232         View edit = view.findViewById(android.R.id.edit);
233         int[] loc = edit.getLocationOnScreen();
234         int x = loc[0] + edit.getWidth() / 2;
235         int y = loc[1] + edit.getHeight() / 2;
236         mQSCustomizer.setEditLocation(x, y);
237     }
238 
239     @Override
setContainer(ViewGroup container)240     public void setContainer(ViewGroup container) {
241         if (container instanceof NotificationsQuickSettingsContainer) {
242             mQSCustomizer.setContainer((NotificationsQuickSettingsContainer) container);
243         }
244     }
245 
246     @Override
isCustomizing()247     public boolean isCustomizing() {
248         return mQSCustomizer.isCustomizing();
249     }
250 
setHost(QSTileHost qsh)251     public void setHost(QSTileHost qsh) {
252         mQSPanel.setHost(qsh, mQSCustomizer);
253         mHeader.setQSPanel(mQSPanel);
254         mFooter.setQSPanel(mQSPanel);
255         mQSDetail.setHost(qsh);
256 
257         if (mQSAnimator != null) {
258             mQSAnimator.setHost(qsh);
259         }
260     }
261 
262     @Override
disable(int displayId, int state1, int state2, boolean animate)263     public void disable(int displayId, int state1, int state2, boolean animate) {
264         if (displayId != getContext().getDisplayId()) {
265             return;
266         }
267         state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
268 
269         final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
270         if (disabled == mQsDisabled) return;
271         mQsDisabled = disabled;
272         mContainer.disable(state1, state2, animate);
273         mHeader.disable(state1, state2, animate);
274         mFooter.disable(state1, state2, animate);
275         updateQsState();
276     }
277 
updateQsState()278     private void updateQsState() {
279         final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling
280                 || mHeaderAnimating;
281         mQSPanel.setExpanded(mQsExpanded);
282         mQSDetail.setExpanded(mQsExpanded);
283         boolean keyguardShowing = isKeyguardShowing();
284         mHeader.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating
285                 || mShowCollapsedOnKeyguard)
286                 ? View.VISIBLE
287                 : View.INVISIBLE);
288         mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
289                 || (mQsExpanded && !mStackScrollerOverscrolling));
290         mFooter.setVisibility(
291                 !mQsDisabled && (mQsExpanded || !keyguardShowing || mHeaderAnimating
292                         || mShowCollapsedOnKeyguard)
293                 ? View.VISIBLE
294                 : View.INVISIBLE);
295         mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
296                 || (mQsExpanded && !mStackScrollerOverscrolling));
297         mQSPanel.setVisibility(!mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE);
298     }
299 
isKeyguardShowing()300     private boolean isKeyguardShowing() {
301         // We want the freshest state here since otherwise we'll have some weirdness if earlier
302         // listeners trigger updates
303         return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
304     }
305 
306     @Override
setShowCollapsedOnKeyguard(boolean showCollapsedOnKeyguard)307     public void setShowCollapsedOnKeyguard(boolean showCollapsedOnKeyguard) {
308         if (showCollapsedOnKeyguard != mShowCollapsedOnKeyguard) {
309             mShowCollapsedOnKeyguard = showCollapsedOnKeyguard;
310             updateQsState();
311             if (mQSAnimator != null) {
312                 mQSAnimator.setShowCollapsedOnKeyguard(showCollapsedOnKeyguard);
313             }
314             if (!showCollapsedOnKeyguard && isKeyguardShowing()) {
315                 setQsExpansion(mLastQSExpansion, 0);
316             }
317         }
318     }
319 
getQsPanel()320     public QSPanel getQsPanel() {
321         return mQSPanel;
322     }
323 
getCustomizer()324     public QSCustomizer getCustomizer() {
325         return mQSCustomizer;
326     }
327 
328     @Override
isShowingDetail()329     public boolean isShowingDetail() {
330         return mQSPanel.isShowingCustomize() || mQSDetail.isShowingDetail();
331     }
332 
333     @Override
setHeaderClickable(boolean clickable)334     public void setHeaderClickable(boolean clickable) {
335         if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
336     }
337 
338     @Override
setExpanded(boolean expanded)339     public void setExpanded(boolean expanded) {
340         if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
341         mQsExpanded = expanded;
342         mQSPanel.setListening(mListening, mQsExpanded);
343         updateQsState();
344     }
345 
setKeyguardShowing(boolean keyguardShowing)346     private void setKeyguardShowing(boolean keyguardShowing) {
347         if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
348         mLastQSExpansion = -1;
349 
350         if (mQSAnimator != null) {
351             mQSAnimator.setOnKeyguard(keyguardShowing);
352         }
353 
354         mFooter.setKeyguardShowing(keyguardShowing);
355         updateQsState();
356     }
357 
358     @Override
setOverscrolling(boolean stackScrollerOverscrolling)359     public void setOverscrolling(boolean stackScrollerOverscrolling) {
360         if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling);
361         mStackScrollerOverscrolling = stackScrollerOverscrolling;
362         updateQsState();
363     }
364 
365     @Override
setListening(boolean listening)366     public void setListening(boolean listening) {
367         if (DEBUG) Log.d(TAG, "setListening " + listening);
368         mListening = listening;
369         mQSContainerImplController.setListening(listening);
370         mHeader.setListening(listening);
371         mFooter.setListening(listening);
372         mQSPanel.setListening(mListening, mQsExpanded);
373     }
374 
375     @Override
setHeaderListening(boolean listening)376     public void setHeaderListening(boolean listening) {
377         mHeader.setListening(listening);
378         mFooter.setListening(listening);
379     }
380 
381     @Override
setQsExpansion(float expansion, float headerTranslation)382     public void setQsExpansion(float expansion, float headerTranslation) {
383         if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
384         mContainer.setExpansion(expansion);
385         final float translationScaleY = expansion - 1;
386         boolean onKeyguardAndExpanded = isKeyguardShowing() && !mShowCollapsedOnKeyguard;
387         if (!mHeaderAnimating && !headerWillBeAnimating()) {
388             getView().setTranslationY(
389                     onKeyguardAndExpanded
390                             ? translationScaleY * mHeader.getHeight()
391                             : headerTranslation);
392         }
393         int currentHeight = getView().getHeight();
394         mLastHeaderTranslation = headerTranslation;
395         if (expansion == mLastQSExpansion && mLastKeyguardAndExpanded == onKeyguardAndExpanded
396                 && mLastViewHeight == currentHeight) {
397             return;
398         }
399         mLastQSExpansion = expansion;
400         mLastKeyguardAndExpanded = onKeyguardAndExpanded;
401         mLastViewHeight = currentHeight;
402 
403         boolean fullyExpanded = expansion == 1;
404         boolean fullyCollapsed = expansion == 0.0f;
405         int heightDiff = mQSPanelScrollView.getBottom() - mHeader.getBottom()
406                 + mHeader.getPaddingBottom();
407         float panelTranslationY = translationScaleY * heightDiff;
408 
409         // Let the views animate their contents correctly by giving them the necessary context.
410         mHeader.setExpansion(onKeyguardAndExpanded, expansion,
411                 panelTranslationY);
412         mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
413         mQSPanel.getQsTileRevealController().setExpansion(expansion);
414         mQSPanel.getTileLayout().setExpansion(expansion);
415         mQSPanelScrollView.setTranslationY(translationScaleY * heightDiff);
416         if (fullyCollapsed) {
417             mQSPanelScrollView.setScrollY(0);
418         }
419         mQSDetail.setFullyExpanded(fullyExpanded);
420 
421         if (!fullyExpanded) {
422             // Set bounds on the QS panel so it doesn't run over the header when animating.
423             mQsBounds.top = (int) -mQSPanelScrollView.getTranslationY();
424             mQsBounds.right = mQSPanelScrollView.getWidth();
425             mQsBounds.bottom = mQSPanelScrollView.getHeight();
426         }
427         updateQsBounds();
428 
429         if (mQSAnimator != null) {
430             mQSAnimator.setPosition(expansion);
431         }
432         updateMediaPositions();
433     }
434 
updateQsBounds()435     private void updateQsBounds() {
436         if (mLastQSExpansion == 1.0f) {
437             // Fully expanded, let's set the layout bounds as clip bounds. This is necessary because
438             // it's a scrollview and otherwise wouldn't be clipped.
439             mQsBounds.set(0, 0, mQSPanelScrollView.getWidth(), mQSPanelScrollView.getHeight());
440         }
441         mQSPanelScrollView.setClipBounds(mQsBounds);
442     }
443 
updateMediaPositions()444     private void updateMediaPositions() {
445         if (Utils.useQsMediaPlayer(getContext())) {
446             mContainer.getLocationOnScreen(mTmpLocation);
447             float absoluteBottomPosition = mTmpLocation[1] + mContainer.getHeight();
448             // The Media can be scrolled off screen by default, let's offset it
449             float expandedMediaPosition = absoluteBottomPosition - mQSPanelScrollView.getScrollY()
450                     + mQSPanelScrollView.getScrollRange();
451             // The expanded media host should never move below the laid out position
452             pinToBottom(expandedMediaPosition, mQSPanel.getMediaHost(), true /* expanded */);
453             // The expanded media host should never move above the laid out position
454             pinToBottom(absoluteBottomPosition, mHeader.getHeaderQsPanel().getMediaHost(),
455                     false /* expanded */);
456         }
457     }
458 
pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded)459     private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) {
460         View hostView = mediaHost.getHostView();
461         if (mLastQSExpansion > 0) {
462             float targetPosition = absoluteBottomPosition - getTotalBottomMargin(hostView)
463                     - hostView.getHeight();
464             float currentPosition = mediaHost.getCurrentBounds().top
465                     - hostView.getTranslationY();
466             float translationY = targetPosition - currentPosition;
467             if (expanded) {
468                 // Never go below the laid out position. This is necessary since the qs panel can
469                 // change in height and we don't want to ever go below it's position
470                 translationY = Math.min(translationY, 0);
471             } else {
472                 translationY = Math.max(translationY, 0);
473             }
474             hostView.setTranslationY(translationY);
475         } else {
476             hostView.setTranslationY(0);
477         }
478     }
479 
getTotalBottomMargin(View startView)480     private float getTotalBottomMargin(View startView) {
481         int result = 0;
482         View child = startView;
483         View parent = (View) startView.getParent();
484         while (!(parent instanceof QSContainerImpl) && parent != null) {
485             result += parent.getHeight() - child.getBottom();
486             child = parent;
487             parent = (View) parent.getParent();
488         }
489         return result;
490     }
491 
headerWillBeAnimating()492     private boolean headerWillBeAnimating() {
493         return mState == StatusBarState.KEYGUARD && mShowCollapsedOnKeyguard
494                 && !isKeyguardShowing();
495     }
496 
497     @Override
animateHeaderSlidingIn(long delay)498     public void animateHeaderSlidingIn(long delay) {
499         if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn");
500         // If the QS is already expanded we don't need to slide in the header as it's already
501         // visible.
502         if (!mQsExpanded && getView().getTranslationY() != 0) {
503             mHeaderAnimating = true;
504             mDelay = delay;
505             getView().getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
506         }
507     }
508 
509     @Override
animateHeaderSlidingOut()510     public void animateHeaderSlidingOut() {
511         if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut");
512         if (getView().getY() == -mHeader.getHeight()) {
513             return;
514         }
515         mHeaderAnimating = true;
516         getView().animate().y(-mHeader.getHeight())
517                 .setStartDelay(0)
518                 .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
519                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
520                 .setListener(new AnimatorListenerAdapter() {
521                     @Override
522                     public void onAnimationEnd(Animator animation) {
523                         if (getView() != null) {
524                             // The view could be destroyed before the animation completes when
525                             // switching users.
526                             getView().animate().setListener(null);
527                         }
528                         mHeaderAnimating = false;
529                         updateQsState();
530                     }
531                 })
532                 .start();
533     }
534 
535     @Override
setExpandClickListener(OnClickListener onClickListener)536     public void setExpandClickListener(OnClickListener onClickListener) {
537         mFooter.setExpandClickListener(onClickListener);
538     }
539 
540     @Override
closeDetail()541     public void closeDetail() {
542         mQSPanel.closeDetail();
543     }
544 
notifyCustomizeChanged()545     public void notifyCustomizeChanged() {
546         // The customize state changed, so our height changed.
547         mContainer.updateExpansion();
548         mQSPanelScrollView.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE
549                 : View.INVISIBLE);
550         mFooter.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
551         // Let the panel know the position changed and it needs to update where notifications
552         // and whatnot are.
553         mPanelView.onQsHeightChanged();
554     }
555 
556     /**
557      * The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
558      * during closing the detail panel, this already returns the smaller height.
559      */
560     @Override
getDesiredHeight()561     public int getDesiredHeight() {
562         if (mQSCustomizer.isCustomizing()) {
563             return getView().getHeight();
564         }
565         if (mQSDetail.isClosingDetail()) {
566             LayoutParams layoutParams = (LayoutParams) mQSPanelScrollView.getLayoutParams();
567             int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin +
568                     + mQSPanelScrollView.getMeasuredHeight();
569             return panelHeight + getView().getPaddingBottom();
570         } else {
571             return getView().getMeasuredHeight();
572         }
573     }
574 
575     @Override
setHeightOverride(int desiredHeight)576     public void setHeightOverride(int desiredHeight) {
577         mContainer.setHeightOverride(desiredHeight);
578     }
579 
580     @Override
getQsMinExpansionHeight()581     public int getQsMinExpansionHeight() {
582         return mHeader.getHeight();
583     }
584 
585     @Override
hideImmediately()586     public void hideImmediately() {
587         getView().animate().cancel();
588         getView().setY(-mHeader.getHeight());
589     }
590 
591     private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
592             = new ViewTreeObserver.OnPreDrawListener() {
593         @Override
594         public boolean onPreDraw() {
595             getView().getViewTreeObserver().removeOnPreDrawListener(this);
596             getView().animate()
597                     .translationY(0f)
598                     .setStartDelay(mDelay)
599                     .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
600                     .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
601                     .setListener(mAnimateHeaderSlidingInListener)
602                     .start();
603             return true;
604         }
605     };
606 
607     private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
608             = new AnimatorListenerAdapter() {
609         @Override
610         public void onAnimationEnd(Animator animation) {
611             mHeaderAnimating = false;
612             updateQsState();
613         }
614     };
615 
616     @Override
onStateChanged(int newState)617     public void onStateChanged(int newState) {
618         mState = newState;
619         setKeyguardShowing(newState == StatusBarState.KEYGUARD);
620     }
621 }
622