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 static com.android.systemui.media.dagger.MediaModule.QS_PANEL; 20 import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL; 21 import static com.android.systemui.statusbar.DisableFlagsLogger.DisableState; 22 23 import android.animation.Animator; 24 import android.animation.AnimatorListenerAdapter; 25 import android.content.res.Configuration; 26 import android.graphics.Rect; 27 import android.os.Bundle; 28 import android.util.Log; 29 import android.view.ContextThemeWrapper; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.View.OnClickListener; 33 import android.view.ViewGroup; 34 import android.view.ViewTreeObserver; 35 import android.widget.FrameLayout.LayoutParams; 36 37 import androidx.annotation.Nullable; 38 import androidx.annotation.VisibleForTesting; 39 40 import com.android.systemui.R; 41 import com.android.systemui.animation.Interpolators; 42 import com.android.systemui.animation.ShadeInterpolation; 43 import com.android.systemui.dump.DumpManager; 44 import com.android.systemui.media.MediaHost; 45 import com.android.systemui.plugins.FalsingManager; 46 import com.android.systemui.plugins.qs.QS; 47 import com.android.systemui.plugins.qs.QSContainerController; 48 import com.android.systemui.plugins.statusbar.StatusBarStateController; 49 import com.android.systemui.qs.customize.QSCustomizerController; 50 import com.android.systemui.qs.dagger.QSFragmentComponent; 51 import com.android.systemui.statusbar.CommandQueue; 52 import com.android.systemui.statusbar.StatusBarState; 53 import com.android.systemui.statusbar.notification.stack.StackStateAnimator; 54 import com.android.systemui.statusbar.phone.KeyguardBypassController; 55 import com.android.systemui.statusbar.policy.BrightnessMirrorController; 56 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; 57 import com.android.systemui.util.LifecycleFragment; 58 import com.android.systemui.util.Utils; 59 60 import java.util.function.Consumer; 61 62 import javax.inject.Inject; 63 import javax.inject.Named; 64 65 public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Callbacks, 66 StatusBarStateController.StateListener { 67 private static final String TAG = "QS"; 68 private static final boolean DEBUG = false; 69 private static final String EXTRA_EXPANDED = "expanded"; 70 private static final String EXTRA_LISTENING = "listening"; 71 72 private final Rect mQsBounds = new Rect(); 73 private final StatusBarStateController mStatusBarStateController; 74 private final FalsingManager mFalsingManager; 75 private final KeyguardBypassController mBypassController; 76 private boolean mQsExpanded; 77 private boolean mHeaderAnimating; 78 private boolean mStackScrollerOverscrolling; 79 80 private long mDelay; 81 82 private QSAnimator mQSAnimator; 83 private HeightListener mPanelView; 84 private QSSquishinessController mQSSquishinessController; 85 protected QuickStatusBarHeader mHeader; 86 protected NonInterceptingScrollView mQSPanelScrollView; 87 private QSDetail mQSDetail; 88 private boolean mListening; 89 private QSContainerImpl mContainer; 90 private int mLayoutDirection; 91 private QSFooter mFooter; 92 private float mLastQSExpansion = -1; 93 private float mLastPanelFraction; 94 private float mSquishinessFraction = 1; 95 private boolean mQsDisabled; 96 97 private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; 98 private final CommandQueue mCommandQueue; 99 private final QSDetailDisplayer mQsDetailDisplayer; 100 private final MediaHost mQsMediaHost; 101 private final MediaHost mQqsMediaHost; 102 private final QSFragmentComponent.Factory mQsComponentFactory; 103 private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger; 104 private final QSTileHost mHost; 105 private boolean mShowCollapsedOnKeyguard; 106 private boolean mLastKeyguardAndExpanded; 107 /** 108 * The last received state from the controller. This should not be used directly to check if 109 * we're on keyguard but use {@link #isKeyguardState()} instead since that is more accurate 110 * during state transitions which often call into us. 111 */ 112 private int mState; 113 private QSContainerImplController mQSContainerImplController; 114 private int[] mTmpLocation = new int[2]; 115 private int mLastViewHeight; 116 private float mLastHeaderTranslation; 117 private QSPanelController mQSPanelController; 118 private QuickQSPanelController mQuickQSPanelController; 119 private QSCustomizerController mQSCustomizerController; 120 private ScrollListener mScrollListener; 121 /** 122 * When true, QS will translate from outside the screen. It will be clipped with parallax 123 * otherwise. 124 */ 125 private boolean mInSplitShade; 126 private boolean mPulseExpanding; 127 128 /** 129 * Are we currently transitioning from lockscreen to the full shade? 130 */ 131 private boolean mTransitioningToFullShade; 132 133 /** 134 * Whether the next Quick settings 135 */ 136 private boolean mAnimateNextQsUpdate; 137 138 private DumpManager mDumpManager; 139 140 /** 141 * Progress of pull down from the center of the lock screen. 142 * @see com.android.systemui.statusbar.LockscreenShadeTransitionController 143 */ 144 private float mFullShadeProgress; 145 146 @Inject QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, QSTileHost qsTileHost, StatusBarStateController statusBarStateController, CommandQueue commandQueue, QSDetailDisplayer qsDetailDisplayer, @Named(QS_PANEL) MediaHost qsMediaHost, @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost, KeyguardBypassController keyguardBypassController, QSFragmentComponent.Factory qsComponentFactory, QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger, FalsingManager falsingManager, DumpManager dumpManager)147 public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, 148 QSTileHost qsTileHost, 149 StatusBarStateController statusBarStateController, CommandQueue commandQueue, 150 QSDetailDisplayer qsDetailDisplayer, @Named(QS_PANEL) MediaHost qsMediaHost, 151 @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost, 152 KeyguardBypassController keyguardBypassController, 153 QSFragmentComponent.Factory qsComponentFactory, 154 QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger, 155 FalsingManager falsingManager, DumpManager dumpManager) { 156 mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; 157 mCommandQueue = commandQueue; 158 mQsDetailDisplayer = qsDetailDisplayer; 159 mQsMediaHost = qsMediaHost; 160 mQqsMediaHost = qqsMediaHost; 161 mQsComponentFactory = qsComponentFactory; 162 mQsFragmentDisableFlagsLogger = qsFragmentDisableFlagsLogger; 163 commandQueue.observe(getLifecycle(), this); 164 mHost = qsTileHost; 165 mFalsingManager = falsingManager; 166 mBypassController = keyguardBypassController; 167 mStatusBarStateController = statusBarStateController; 168 mDumpManager = dumpManager; 169 } 170 171 @Override onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState)172 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 173 Bundle savedInstanceState) { 174 inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), 175 R.style.Theme_SystemUI_QuickSettings)); 176 return inflater.inflate(R.layout.qs_panel, container, false); 177 } 178 179 @Override onViewCreated(View view, @Nullable Bundle savedInstanceState)180 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 181 QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this); 182 mQSPanelController = qsFragmentComponent.getQSPanelController(); 183 mQuickQSPanelController = qsFragmentComponent.getQuickQSPanelController(); 184 185 mQSPanelController.init(); 186 mQuickQSPanelController.init(); 187 188 mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view); 189 mQSPanelScrollView.addOnLayoutChangeListener( 190 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 191 updateQsBounds(); 192 }); 193 mQSPanelScrollView.setOnScrollChangeListener( 194 (v, scrollX, scrollY, oldScrollX, oldScrollY) -> { 195 // Lazily update animators whenever the scrolling changes 196 mQSAnimator.requestAnimatorUpdate(); 197 mHeader.setExpandedScrollAmount(scrollY); 198 if (mScrollListener != null) { 199 mScrollListener.onQsPanelScrollChanged(scrollY); 200 } 201 }); 202 mQSDetail = view.findViewById(R.id.qs_detail); 203 mHeader = view.findViewById(R.id.header); 204 mQSPanelController.setHeaderContainer(view.findViewById(R.id.header_text_container)); 205 mFooter = qsFragmentComponent.getQSFooter(); 206 207 mQsDetailDisplayer.setQsPanelController(mQSPanelController); 208 209 mQSContainerImplController = qsFragmentComponent.getQSContainerImplController(); 210 mQSContainerImplController.init(); 211 mContainer = mQSContainerImplController.getView(); 212 mDumpManager.registerDumpable(mContainer.getClass().getName(), mContainer); 213 214 mQSDetail.setQsPanel(mQSPanelController, mHeader, mFooter, mFalsingManager); 215 mQSAnimator = qsFragmentComponent.getQSAnimator(); 216 mQSSquishinessController = qsFragmentComponent.getQSSquishinessController(); 217 218 mQSCustomizerController = qsFragmentComponent.getQSCustomizerController(); 219 mQSCustomizerController.init(); 220 mQSCustomizerController.setQs(this); 221 if (savedInstanceState != null) { 222 setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED)); 223 setListening(savedInstanceState.getBoolean(EXTRA_LISTENING)); 224 setEditLocation(view); 225 mQSCustomizerController.restoreInstanceState(savedInstanceState); 226 if (mQsExpanded) { 227 mQSPanelController.getTileLayout().restoreInstanceState(savedInstanceState); 228 } 229 } 230 setHost(mHost); 231 mStatusBarStateController.addCallback(this); 232 onStateChanged(mStatusBarStateController.getState()); 233 view.addOnLayoutChangeListener( 234 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 235 boolean sizeChanged = (oldTop - oldBottom) != (top - bottom); 236 if (sizeChanged) { 237 setQsExpansion(mLastQSExpansion, mLastPanelFraction, 238 mLastHeaderTranslation, mSquishinessFraction); 239 } 240 }); 241 mQSPanelController.setUsingHorizontalLayoutChangeListener( 242 () -> { 243 // The hostview may be faded out in the horizontal layout. Let's make sure to 244 // reset the alpha when switching layouts. This is fine since the animator will 245 // update the alpha if it's not supposed to be 1.0f 246 mQSPanelController.getMediaHost().getHostView().setAlpha(1.0f); 247 mQSAnimator.requestAnimatorUpdate(); 248 }); 249 } 250 251 @Override setScrollListener(ScrollListener listener)252 public void setScrollListener(ScrollListener listener) { 253 mScrollListener = listener; 254 } 255 256 @Override onDestroy()257 public void onDestroy() { 258 super.onDestroy(); 259 mStatusBarStateController.removeCallback(this); 260 if (mListening) { 261 setListening(false); 262 } 263 mQSCustomizerController.setQs(null); 264 mQsDetailDisplayer.setQsPanelController(null); 265 mScrollListener = null; 266 mDumpManager.unregisterDumpable(mContainer.getClass().getName()); 267 } 268 269 @Override onSaveInstanceState(Bundle outState)270 public void onSaveInstanceState(Bundle outState) { 271 super.onSaveInstanceState(outState); 272 outState.putBoolean(EXTRA_EXPANDED, mQsExpanded); 273 outState.putBoolean(EXTRA_LISTENING, mListening); 274 mQSCustomizerController.saveInstanceState(outState); 275 if (mQsExpanded) { 276 mQSPanelController.getTileLayout().saveInstanceState(outState); 277 } 278 } 279 280 @VisibleForTesting isListening()281 boolean isListening() { 282 return mListening; 283 } 284 285 @VisibleForTesting isExpanded()286 boolean isExpanded() { 287 return mQsExpanded; 288 } 289 290 @Override getHeader()291 public View getHeader() { 292 return mHeader; 293 } 294 295 @Override setHasNotifications(boolean hasNotifications)296 public void setHasNotifications(boolean hasNotifications) { 297 } 298 299 @Override setPanelView(HeightListener panelView)300 public void setPanelView(HeightListener panelView) { 301 mPanelView = panelView; 302 } 303 304 @Override onConfigurationChanged(Configuration newConfig)305 public void onConfigurationChanged(Configuration newConfig) { 306 super.onConfigurationChanged(newConfig); 307 setEditLocation(getView()); 308 if (newConfig.getLayoutDirection() != mLayoutDirection) { 309 mLayoutDirection = newConfig.getLayoutDirection(); 310 if (mQSAnimator != null) { 311 mQSAnimator.onRtlChanged(); 312 } 313 } 314 updateQsState(); 315 } 316 317 @Override setFancyClipping(int top, int bottom, int cornerRadius, boolean visible)318 public void setFancyClipping(int top, int bottom, int cornerRadius, boolean visible) { 319 if (getView() instanceof QSContainerImpl) { 320 ((QSContainerImpl) getView()).setFancyClipping(top, bottom, cornerRadius, visible); 321 } 322 } 323 324 @Override isFullyCollapsed()325 public boolean isFullyCollapsed() { 326 return mLastQSExpansion == 0.0f || mLastQSExpansion == -1; 327 } 328 329 @Override setCollapsedMediaVisibilityChangedListener(Consumer<Boolean> listener)330 public void setCollapsedMediaVisibilityChangedListener(Consumer<Boolean> listener) { 331 mQuickQSPanelController.setMediaVisibilityChangedListener(listener); 332 } 333 setEditLocation(View view)334 private void setEditLocation(View view) { 335 View edit = view.findViewById(android.R.id.edit); 336 int[] loc = edit.getLocationOnScreen(); 337 int x = loc[0] + edit.getWidth() / 2; 338 int y = loc[1] + edit.getHeight() / 2; 339 mQSCustomizerController.setEditLocation(x, y); 340 } 341 342 @Override setContainerController(QSContainerController controller)343 public void setContainerController(QSContainerController controller) { 344 mQSCustomizerController.setContainerController(controller); 345 mQSDetail.setContainerController(controller); 346 } 347 348 @Override isCustomizing()349 public boolean isCustomizing() { 350 return mQSCustomizerController.isCustomizing(); 351 } 352 setHost(QSTileHost qsh)353 public void setHost(QSTileHost qsh) { 354 mQSDetail.setHost(qsh); 355 } 356 357 @Override disable(int displayId, int state1, int state2, boolean animate)358 public void disable(int displayId, int state1, int state2, boolean animate) { 359 if (displayId != getContext().getDisplayId()) { 360 return; 361 } 362 int state2BeforeAdjustment = state2; 363 state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2); 364 365 mQsFragmentDisableFlagsLogger.logDisableFlagChange( 366 /* new= */ new DisableState(state1, state2BeforeAdjustment), 367 /* newAfterLocalModification= */ new DisableState(state1, state2) 368 ); 369 370 final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0; 371 if (disabled == mQsDisabled) return; 372 mQsDisabled = disabled; 373 mContainer.disable(state1, state2, animate); 374 mHeader.disable(state1, state2, animate); 375 mFooter.disable(state1, state2, animate); 376 updateQsState(); 377 } 378 updateQsState()379 private void updateQsState() { 380 final boolean expanded = mQsExpanded || mInSplitShade; 381 final boolean expandVisually = expanded || mStackScrollerOverscrolling 382 || mHeaderAnimating; 383 mQSPanelController.setExpanded(expanded); 384 mQSDetail.setExpanded(expanded); 385 boolean keyguardShowing = isKeyguardState(); 386 mHeader.setVisibility((expanded || !keyguardShowing || mHeaderAnimating 387 || mShowCollapsedOnKeyguard) 388 ? View.VISIBLE 389 : View.INVISIBLE); 390 mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) 391 || (expanded && !mStackScrollerOverscrolling), mQuickQSPanelController); 392 mFooter.setVisibility(!mQsDisabled && (expanded || !keyguardShowing || mHeaderAnimating 393 || mShowCollapsedOnKeyguard) 394 ? View.VISIBLE 395 : View.INVISIBLE); 396 mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) 397 || (expanded && !mStackScrollerOverscrolling)); 398 mQSPanelController.setVisibility( 399 !mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE); 400 } 401 isKeyguardState()402 private boolean isKeyguardState() { 403 // We want the freshest state here since otherwise we'll have some weirdness if earlier 404 // listeners trigger updates 405 return mStatusBarStateController.getState() == StatusBarState.KEYGUARD; 406 } 407 updateShowCollapsedOnKeyguard()408 private void updateShowCollapsedOnKeyguard() { 409 boolean showCollapsed = mBypassController.getBypassEnabled() 410 || (mTransitioningToFullShade && !mInSplitShade); 411 if (showCollapsed != mShowCollapsedOnKeyguard) { 412 mShowCollapsedOnKeyguard = showCollapsed; 413 updateQsState(); 414 if (mQSAnimator != null) { 415 mQSAnimator.setShowCollapsedOnKeyguard(showCollapsed); 416 } 417 if (!showCollapsed && isKeyguardState()) { 418 setQsExpansion(mLastQSExpansion, mLastPanelFraction, 0, 419 mSquishinessFraction); 420 } 421 } 422 } 423 getQSPanelController()424 public QSPanelController getQSPanelController() { 425 return mQSPanelController; 426 } 427 setBrightnessMirrorController( BrightnessMirrorController brightnessMirrorController)428 public void setBrightnessMirrorController( 429 BrightnessMirrorController brightnessMirrorController) { 430 mQSPanelController.setBrightnessMirror(brightnessMirrorController); 431 } 432 433 @Override isShowingDetail()434 public boolean isShowingDetail() { 435 return mQSCustomizerController.isCustomizing() || mQSDetail.isShowingDetail(); 436 } 437 438 @Override setHeaderClickable(boolean clickable)439 public void setHeaderClickable(boolean clickable) { 440 if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable); 441 } 442 443 @Override setExpanded(boolean expanded)444 public void setExpanded(boolean expanded) { 445 if (DEBUG) Log.d(TAG, "setExpanded " + expanded); 446 mQsExpanded = expanded; 447 mQSPanelController.setListening(mListening, mQsExpanded); 448 updateQsState(); 449 } 450 setKeyguardShowing(boolean keyguardShowing)451 private void setKeyguardShowing(boolean keyguardShowing) { 452 if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing); 453 mLastQSExpansion = -1; 454 455 if (mQSAnimator != null) { 456 mQSAnimator.setOnKeyguard(keyguardShowing); 457 } 458 459 mFooter.setKeyguardShowing(keyguardShowing); 460 updateQsState(); 461 } 462 463 @Override setOverscrolling(boolean stackScrollerOverscrolling)464 public void setOverscrolling(boolean stackScrollerOverscrolling) { 465 if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling); 466 mStackScrollerOverscrolling = stackScrollerOverscrolling; 467 updateQsState(); 468 } 469 470 @Override setListening(boolean listening)471 public void setListening(boolean listening) { 472 if (DEBUG) Log.d(TAG, "setListening " + listening); 473 mListening = listening; 474 mQSContainerImplController.setListening(listening); 475 mFooter.setListening(listening); 476 mQSPanelController.setListening(mListening, mQsExpanded); 477 } 478 479 @Override setHeaderListening(boolean listening)480 public void setHeaderListening(boolean listening) { 481 mQSContainerImplController.setListening(listening); 482 mFooter.setListening(listening); 483 } 484 485 @Override setInSplitShade(boolean inSplitShade)486 public void setInSplitShade(boolean inSplitShade) { 487 mInSplitShade = inSplitShade; 488 mQSAnimator.setTranslateWhileExpanding(inSplitShade); 489 updateShowCollapsedOnKeyguard(); 490 updateQsState(); 491 } 492 493 @Override setTransitionToFullShadeAmount(float pxAmount, float progress)494 public void setTransitionToFullShadeAmount(float pxAmount, float progress) { 495 boolean isTransitioningToFullShade = pxAmount > 0; 496 if (isTransitioningToFullShade != mTransitioningToFullShade) { 497 mTransitioningToFullShade = isTransitioningToFullShade; 498 updateShowCollapsedOnKeyguard(); 499 } 500 mFullShadeProgress = progress; 501 setQsExpansion(mLastQSExpansion, mLastPanelFraction, mLastHeaderTranslation, 502 isTransitioningToFullShade ? progress : mSquishinessFraction); 503 } 504 505 @Override setQsExpansion(float expansion, float panelExpansionFraction, float proposedTranslation, float squishinessFraction)506 public void setQsExpansion(float expansion, float panelExpansionFraction, 507 float proposedTranslation, float squishinessFraction) { 508 float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation; 509 float progress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD 510 ? mFullShadeProgress : panelExpansionFraction; 511 setAlphaAnimationProgress(mInSplitShade ? progress : 1); 512 mContainer.setExpansion(expansion); 513 final float translationScaleY = (mInSplitShade 514 ? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1); 515 boolean onKeyguardAndExpanded = isKeyguardState() && !mShowCollapsedOnKeyguard; 516 if (!mHeaderAnimating && !headerWillBeAnimating()) { 517 getView().setTranslationY( 518 onKeyguardAndExpanded 519 ? translationScaleY * mHeader.getHeight() 520 : headerTranslation); 521 } 522 int currentHeight = getView().getHeight(); 523 if (expansion == mLastQSExpansion 524 && mLastKeyguardAndExpanded == onKeyguardAndExpanded 525 && mLastViewHeight == currentHeight 526 && mLastHeaderTranslation == headerTranslation 527 && mSquishinessFraction == squishinessFraction) { 528 return; 529 } 530 mLastHeaderTranslation = headerTranslation; 531 mLastPanelFraction = panelExpansionFraction; 532 mSquishinessFraction = squishinessFraction; 533 mLastQSExpansion = expansion; 534 mLastKeyguardAndExpanded = onKeyguardAndExpanded; 535 mLastViewHeight = currentHeight; 536 537 boolean fullyExpanded = expansion == 1; 538 boolean fullyCollapsed = expansion == 0.0f; 539 int heightDiff = mQSPanelScrollView.getBottom() - mHeader.getBottom() 540 + mHeader.getPaddingBottom(); 541 float panelTranslationY = translationScaleY * heightDiff; 542 543 // Let the views animate their contents correctly by giving them the necessary context. 544 mHeader.setExpansion(onKeyguardAndExpanded, expansion, panelTranslationY); 545 if (expansion < 1 && expansion > 0.99) { 546 if (mQuickQSPanelController.switchTileLayout(false)) { 547 mHeader.updateResources(); 548 } 549 } 550 mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion); 551 mQSPanelController.setRevealExpansion(expansion); 552 mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); 553 mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); 554 mQSPanelScrollView.setTranslationY(translationScaleY * heightDiff); 555 if (fullyCollapsed) { 556 mQSPanelScrollView.setScrollY(0); 557 } 558 mQSDetail.setFullyExpanded(fullyExpanded); 559 560 if (!fullyExpanded) { 561 // Set bounds on the QS panel so it doesn't run over the header when animating. 562 mQsBounds.top = (int) -mQSPanelScrollView.getTranslationY(); 563 mQsBounds.right = mQSPanelScrollView.getWidth(); 564 mQsBounds.bottom = mQSPanelScrollView.getHeight(); 565 } 566 updateQsBounds(); 567 568 if (mQSSquishinessController != null) { 569 mQSSquishinessController.setSquishiness(mSquishinessFraction); 570 } 571 if (mQSAnimator != null) { 572 mQSAnimator.setPosition(expansion); 573 } 574 updateMediaPositions(); 575 } 576 setAlphaAnimationProgress(float progress)577 private void setAlphaAnimationProgress(float progress) { 578 final View view = getView(); 579 if (progress == 0 && view.getVisibility() != View.INVISIBLE) { 580 view.setVisibility(View.INVISIBLE); 581 } else if (progress > 0 && view.getVisibility() != View.VISIBLE) { 582 view.setVisibility((View.VISIBLE)); 583 } 584 float alpha = ShadeInterpolation.getContentAlpha(progress); 585 view.setAlpha(alpha); 586 } 587 updateQsBounds()588 private void updateQsBounds() { 589 if (mLastQSExpansion == 1.0f) { 590 // Fully expanded, let's set the layout bounds as clip bounds. This is necessary because 591 // it's a scrollview and otherwise wouldn't be clipped. However, we set the horizontal 592 // bounds so the pages go to the ends of QSContainerImpl 593 ViewGroup.MarginLayoutParams lp = 594 (ViewGroup.MarginLayoutParams) mQSPanelScrollView.getLayoutParams(); 595 mQsBounds.set(-lp.leftMargin, 0, mQSPanelScrollView.getWidth() + lp.rightMargin, 596 mQSPanelScrollView.getHeight()); 597 } 598 mQSPanelScrollView.setClipBounds(mQsBounds); 599 } 600 updateMediaPositions()601 private void updateMediaPositions() { 602 if (Utils.useQsMediaPlayer(getContext())) { 603 mContainer.getLocationOnScreen(mTmpLocation); 604 float absoluteBottomPosition = mTmpLocation[1] + mContainer.getHeight(); 605 // The Media can be scrolled off screen by default, let's offset it 606 float expandedMediaPosition = absoluteBottomPosition - mQSPanelScrollView.getScrollY() 607 + mQSPanelScrollView.getScrollRange(); 608 pinToBottom(expandedMediaPosition, mQsMediaHost, true /* expanded */); 609 // The expanded media host should never move above the laid out position 610 pinToBottom(absoluteBottomPosition, mQqsMediaHost, false /* expanded */); 611 } 612 } 613 pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded)614 private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) { 615 View hostView = mediaHost.getHostView(); 616 // On keyguard we cross-fade to expanded, so no need to pin it. 617 // If the collapsed qs isn't visible, we also just keep it at the laid out position. 618 if (mLastQSExpansion > 0 && !isKeyguardState() && mQqsMediaHost.getVisible()) { 619 float targetPosition = absoluteBottomPosition - getTotalBottomMargin(hostView) 620 - hostView.getHeight(); 621 float currentPosition = mediaHost.getCurrentBounds().top 622 - hostView.getTranslationY(); 623 float translationY = targetPosition - currentPosition; 624 if (expanded) { 625 // Never go below the laid out position. This is necessary since the qs panel can 626 // change in height and we don't want to ever go below it's position 627 translationY = Math.min(translationY, 0); 628 } else { 629 translationY = Math.max(translationY, 0); 630 } 631 hostView.setTranslationY(translationY); 632 } else { 633 hostView.setTranslationY(0); 634 } 635 } 636 getTotalBottomMargin(View startView)637 private float getTotalBottomMargin(View startView) { 638 int result = 0; 639 View child = startView; 640 View parent = (View) startView.getParent(); 641 while (!(parent instanceof QSContainerImpl) && parent != null) { 642 result += parent.getHeight() - child.getBottom(); 643 child = parent; 644 parent = (View) parent.getParent(); 645 } 646 return result; 647 } 648 headerWillBeAnimating()649 private boolean headerWillBeAnimating() { 650 return mState == StatusBarState.KEYGUARD && mShowCollapsedOnKeyguard 651 && !isKeyguardState(); 652 } 653 654 @Override animateHeaderSlidingOut()655 public void animateHeaderSlidingOut() { 656 if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut"); 657 if (getView().getY() == -mHeader.getHeight()) { 658 return; 659 } 660 mHeaderAnimating = true; 661 getView().animate().y(-mHeader.getHeight()) 662 .setStartDelay(0) 663 .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD) 664 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 665 .setListener(new AnimatorListenerAdapter() { 666 @Override 667 public void onAnimationEnd(Animator animation) { 668 if (getView() != null) { 669 // The view could be destroyed before the animation completes when 670 // switching users. 671 getView().animate().setListener(null); 672 } 673 mHeaderAnimating = false; 674 updateQsState(); 675 } 676 }) 677 .start(); 678 } 679 680 @Override setExpandClickListener(OnClickListener onClickListener)681 public void setExpandClickListener(OnClickListener onClickListener) { 682 mFooter.setExpandClickListener(onClickListener); 683 } 684 685 @Override closeDetail()686 public void closeDetail() { 687 mQSPanelController.closeDetail(); 688 } 689 690 @Override closeCustomizer()691 public void closeCustomizer() { 692 mQSCustomizerController.hide(); 693 } 694 notifyCustomizeChanged()695 public void notifyCustomizeChanged() { 696 // The customize state changed, so our height changed. 697 mContainer.updateExpansion(); 698 boolean customizing = isCustomizing(); 699 mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); 700 mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); 701 mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); 702 // Let the panel know the position changed and it needs to update where notifications 703 // and whatnot are. 704 mPanelView.onQsHeightChanged(); 705 } 706 707 /** 708 * The height this view wants to be. This is different from {@link View#getMeasuredHeight} such 709 * that during closing the detail panel, this already returns the smaller height. 710 */ 711 @Override getDesiredHeight()712 public int getDesiredHeight() { 713 if (mQSCustomizerController.isCustomizing()) { 714 return getView().getHeight(); 715 } 716 if (mQSDetail.isClosingDetail()) { 717 LayoutParams layoutParams = (LayoutParams) mQSPanelScrollView.getLayoutParams(); 718 int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin + 719 + mQSPanelScrollView.getMeasuredHeight(); 720 return panelHeight + getView().getPaddingBottom(); 721 } else { 722 return getView().getMeasuredHeight(); 723 } 724 } 725 726 @Override setHeightOverride(int desiredHeight)727 public void setHeightOverride(int desiredHeight) { 728 mContainer.setHeightOverride(desiredHeight); 729 } 730 731 @Override getQsMinExpansionHeight()732 public int getQsMinExpansionHeight() { 733 return mHeader.getHeight(); 734 } 735 736 @Override hideImmediately()737 public void hideImmediately() { 738 getView().animate().cancel(); 739 getView().setY(-mHeader.getHeight()); 740 } 741 742 private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn 743 = new ViewTreeObserver.OnPreDrawListener() { 744 @Override 745 public boolean onPreDraw() { 746 getView().getViewTreeObserver().removeOnPreDrawListener(this); 747 getView().animate() 748 .translationY(0f) 749 .setStartDelay(mDelay) 750 .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE) 751 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 752 .setListener(mAnimateHeaderSlidingInListener) 753 .start(); 754 return true; 755 } 756 }; 757 758 private final Animator.AnimatorListener mAnimateHeaderSlidingInListener 759 = new AnimatorListenerAdapter() { 760 @Override 761 public void onAnimationEnd(Animator animation) { 762 mHeaderAnimating = false; 763 updateQsState(); 764 // Unset the listener, otherwise this may persist for another view property animation 765 getView().animate().setListener(null); 766 } 767 }; 768 769 @Override onStateChanged(int newState)770 public void onStateChanged(int newState) { 771 mState = newState; 772 setKeyguardShowing(newState == StatusBarState.KEYGUARD); 773 updateShowCollapsedOnKeyguard(); 774 } 775 } 776