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