1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.qs; 18 19 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; 20 21 import static com.android.systemui.media.dagger.MediaModule.QS_PANEL; 22 import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL; 23 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; 24 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; 25 26 import android.animation.Animator; 27 import android.animation.AnimatorListenerAdapter; 28 import android.content.Context; 29 import android.content.res.Configuration; 30 import android.content.res.Resources; 31 import android.graphics.Rect; 32 import android.os.Bundle; 33 import android.util.IndentingPrintWriter; 34 import android.util.Log; 35 import android.view.View; 36 import android.view.ViewGroup; 37 38 import androidx.annotation.FloatRange; 39 import androidx.annotation.Nullable; 40 import androidx.annotation.VisibleForTesting; 41 import androidx.compose.ui.platform.ComposeView; 42 import androidx.lifecycle.Lifecycle; 43 import androidx.lifecycle.LifecycleOwner; 44 import androidx.lifecycle.LifecycleRegistry; 45 46 import com.android.app.animation.Interpolators; 47 import com.android.keyguard.BouncerPanelExpansionCalculator; 48 import com.android.systemui.Dumpable; 49 import com.android.systemui.animation.ShadeInterpolation; 50 import com.android.systemui.dump.DumpManager; 51 import com.android.systemui.media.controls.ui.view.MediaHost; 52 import com.android.systemui.plugins.qs.QS; 53 import com.android.systemui.plugins.qs.QSContainerController; 54 import com.android.systemui.plugins.statusbar.StatusBarStateController; 55 import com.android.systemui.qs.customize.QSCustomizerController; 56 import com.android.systemui.qs.dagger.QSComponent; 57 import com.android.systemui.qs.flags.QSComposeFragment; 58 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; 59 import com.android.systemui.qs.logging.QSLogger; 60 import com.android.systemui.res.R; 61 import com.android.systemui.scene.shared.flag.SceneContainerFlag; 62 import com.android.systemui.settings.brightness.MirrorController; 63 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; 64 import com.android.systemui.statusbar.CommandQueue; 65 import com.android.systemui.statusbar.StatusBarState; 66 import com.android.systemui.statusbar.SysuiStatusBarStateController; 67 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; 68 import com.android.systemui.statusbar.notification.stack.StackStateAnimator; 69 import com.android.systemui.statusbar.phone.KeyguardBypassController; 70 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; 71 import com.android.systemui.util.Utils; 72 73 import dalvik.annotation.optimization.NeverCompile; 74 75 import java.io.PrintWriter; 76 import java.util.Arrays; 77 import java.util.function.Consumer; 78 79 import javax.inject.Inject; 80 import javax.inject.Named; 81 82 public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateController.StateListener, 83 Dumpable { 84 private static final String TAG = "QS"; 85 private static final boolean DEBUG = false; 86 private static final String EXTRA_EXPANDED = "expanded"; 87 private static final String EXTRA_LISTENING = "listening"; 88 private static final String EXTRA_VISIBLE = "visible"; 89 90 private final Rect mQsBounds = new Rect(); 91 private final SysuiStatusBarStateController mStatusBarStateController; 92 private final KeyguardBypassController mBypassController; 93 private boolean mQsExpanded; 94 private boolean mHeaderAnimating; 95 private boolean mStackScrollerOverscrolling; 96 97 private QSAnimator mQSAnimator; 98 @Nullable 99 private HeightListener mPanelView; 100 private QSSquishinessController mQSSquishinessController; 101 protected QuickStatusBarHeader mHeader; 102 protected NonInterceptingScrollView mQSPanelScrollView; 103 private boolean mListening; 104 private QSContainerImpl mContainer; 105 private int mLayoutDirection; 106 private QSFooter mFooter; 107 private float mLastQSExpansion = -1; 108 private float mLastPanelFraction; 109 private float mSquishinessFraction = 1; 110 private boolean mQsDisabled; 111 private int[] mLocationTemp = new int[2]; 112 113 private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; 114 private final MediaHost mQsMediaHost; 115 private final MediaHost mQqsMediaHost; 116 private final QSDisableFlagsLogger mQsDisableFlagsLogger; 117 private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; 118 private final QSLogger mLogger; 119 private final FooterActionsController mFooterActionsController; 120 private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory; 121 private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner; 122 private boolean mShowCollapsedOnKeyguard; 123 private boolean mLastKeyguardAndExpanded; 124 /** 125 * The last received state from the controller. This should not be used directly to check if 126 * we're on keyguard but use {@link #isKeyguardState()} instead since that is more accurate 127 * during state transitions which often call into us. 128 */ 129 private int mStatusBarState = -1; 130 private QSContainerImplController mQSContainerImplController; 131 private int[] mTmpLocation = new int[2]; 132 private int mLastViewHeight; 133 private float mLastHeaderTranslation; 134 private QSPanelController mQSPanelController; 135 private QuickQSPanelController mQuickQSPanelController; 136 private QSCustomizerController mQSCustomizerController; 137 private FooterActionsViewModel mQSFooterActionsViewModel; 138 @Nullable 139 private ScrollListener mScrollListener; 140 /** 141 * When true, QS will translate from outside the screen. It will be clipped with parallax 142 * otherwise. 143 */ 144 private boolean mInSplitShade; 145 146 /** 147 * Are we currently transitioning from lockscreen to the full shade? 148 */ 149 private boolean mTransitioningToFullShade; 150 151 private final DumpManager mDumpManager; 152 153 /** 154 * Progress of pull down from the center of the lock screen. 155 * @see com.android.systemui.statusbar.LockscreenShadeTransitionController 156 */ 157 private float mLockscreenToShadeProgress; 158 159 private boolean mOverScrolling; 160 161 // Whether QQS or QS is visible. When in lockscreen, this is true if and only if QQS or QS is 162 // visible; 163 private boolean mQsVisible; 164 165 private boolean mIsSmallScreen; 166 167 /** Should the squishiness fraction be updated on the media host. */ 168 private boolean mShouldUpdateMediaSquishiness; 169 170 private CommandQueue mCommandQueue; 171 172 private View mRootView; 173 @Nullable 174 private ComposeView mFooterActionsView; 175 176 @Inject QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue, @Named(QS_PANEL) MediaHost qsMediaHost, @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost, KeyguardBypassController keyguardBypassController, QSDisableFlagsLogger qsDisableFlagsLogger, DumpManager dumpManager, QSLogger qsLogger, FooterActionsController footerActionsController, FooterActionsViewModel.Factory footerActionsViewModelFactory, LargeScreenShadeInterpolator largeScreenShadeInterpolator)177 public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, 178 SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue, 179 @Named(QS_PANEL) MediaHost qsMediaHost, 180 @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost, 181 KeyguardBypassController keyguardBypassController, 182 QSDisableFlagsLogger qsDisableFlagsLogger, 183 DumpManager dumpManager, QSLogger qsLogger, 184 FooterActionsController footerActionsController, 185 FooterActionsViewModel.Factory footerActionsViewModelFactory, 186 LargeScreenShadeInterpolator largeScreenShadeInterpolator) { 187 mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; 188 mQsMediaHost = qsMediaHost; 189 mQqsMediaHost = qqsMediaHost; 190 mQsDisableFlagsLogger = qsDisableFlagsLogger; 191 mLogger = qsLogger; 192 mLargeScreenShadeInterpolator = largeScreenShadeInterpolator; 193 mCommandQueue = commandQueue; 194 mBypassController = keyguardBypassController; 195 mStatusBarStateController = statusBarStateController; 196 mDumpManager = dumpManager; 197 mFooterActionsController = footerActionsController; 198 mFooterActionsViewModelFactory = footerActionsViewModelFactory; 199 mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner(); 200 if (SceneContainerFlag.isEnabled()) { 201 mStatusBarState = StatusBarState.SHADE; 202 } 203 } 204 205 /** 206 * This method will set up all the necessary fields. Methods from the implemented interfaces 207 * should not be called before this method returns. 208 */ onComponentCreated(QSComponent qsComponent, @Nullable Bundle savedInstanceState)209 public void onComponentCreated(QSComponent qsComponent, @Nullable Bundle savedInstanceState) { 210 mRootView = qsComponent.getRootView(); 211 212 mQSPanelController = qsComponent.getQSPanelController(); 213 mQuickQSPanelController = qsComponent.getQuickQSPanelController(); 214 215 mQSPanelController.init(); 216 mQuickQSPanelController.init(); 217 218 if (!SceneContainerFlag.isEnabled()) { 219 mQSFooterActionsViewModel = mFooterActionsViewModelFactory 220 .create(mListeningAndVisibilityLifecycleOwner); 221 bindFooterActionsView(mRootView); 222 mFooterActionsController.init(); 223 } else { 224 View footerView = mRootView.findViewById(R.id.qs_footer_actions); 225 if (footerView != null) { 226 ((ViewGroup) footerView.getParent()).removeView(footerView); 227 } 228 } 229 230 mQSPanelScrollView = mRootView.findViewById(R.id.expanded_qs_scroll_view); 231 mQSPanelScrollView.addOnLayoutChangeListener( 232 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 233 updateQsBounds(); 234 }); 235 mQSPanelScrollView.setOnScrollChangeListener( 236 (v, scrollX, scrollY, oldScrollX, oldScrollY) -> { 237 // Lazily update animators whenever the scrolling changes 238 mQSAnimator.requestAnimatorUpdate(); 239 if (mScrollListener != null) { 240 mScrollListener.onQsPanelScrollChanged(scrollY); 241 } 242 }); 243 mQSPanelScrollView.setScrollingEnabled(!SceneContainerFlag.isEnabled()); 244 mHeader = mRootView.findViewById(R.id.header); 245 mFooter = qsComponent.getQSFooter(); 246 247 mQSContainerImplController = qsComponent.getQSContainerImplController(); 248 mQSContainerImplController.init(); 249 mContainer = mQSContainerImplController.getView(); 250 mDumpManager.registerDumpable(mContainer.getClass().getSimpleName(), mContainer); 251 252 mQSAnimator = qsComponent.getQSAnimator(); 253 mQSSquishinessController = qsComponent.getQSSquishinessController(); 254 255 mQSCustomizerController = qsComponent.getQSCustomizerController(); 256 mQSCustomizerController.init(); 257 mQSCustomizerController.setQs(this); 258 if (savedInstanceState != null) { 259 setQsVisible(savedInstanceState.getBoolean(EXTRA_VISIBLE)); 260 setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED)); 261 setListening(savedInstanceState.getBoolean(EXTRA_LISTENING)); 262 setEditLocation(mRootView); 263 mQSCustomizerController.restoreInstanceState(savedInstanceState); 264 if (mQsExpanded) { 265 mQSPanelController.getTileLayout().restoreInstanceState(savedInstanceState); 266 } 267 } 268 mStatusBarStateController.addCallback(this); 269 onStateChanged(mStatusBarStateController.getState()); 270 mRootView.addOnLayoutChangeListener( 271 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 272 boolean sizeChanged = (oldTop - oldBottom) != (top - bottom); 273 if (sizeChanged) { 274 setQsExpansion(mLastQSExpansion, mLastPanelFraction, 275 mLastHeaderTranslation, mSquishinessFraction); 276 } 277 }); 278 mQSPanelController.setUsingHorizontalLayoutChangeListener( 279 () -> { 280 // The hostview may be faded out in the horizontal layout. Let's make sure to 281 // reset the alpha when switching layouts. This is fine since the animator will 282 // update the alpha if it's not supposed to be 1.0f 283 mQSPanelController.getMediaHost().getHostView().setAlpha(1.0f); 284 mQSAnimator.requestAnimatorUpdate(); 285 }); 286 287 // This will immediately call disable, so it needs to be added after setting up the fields. 288 mCommandQueue.addCallback(this); 289 } 290 bindFooterActionsView(View root)291 private void bindFooterActionsView(View root) { 292 mFooterActionsView = root.findViewById(R.id.qs_footer_actions); 293 QSUtils.setFooterActionsViewContent(mFooterActionsView, 294 mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner); 295 } 296 297 @Override setScrollListener(ScrollListener listener)298 public void setScrollListener(ScrollListener listener) { 299 mScrollListener = listener; 300 } 301 onCreate(Bundle savedInstanceState)302 public void onCreate(Bundle savedInstanceState) { 303 mDumpManager.registerDumpable(getClass().getSimpleName(), this); 304 } 305 onDestroy()306 public void onDestroy() { 307 mCommandQueue.removeCallback(this); 308 mStatusBarStateController.removeCallback(this); 309 mQSPanelController.destroy(); 310 mQuickQSPanelController.destroy(); 311 if (mListening) { 312 setListening(false); 313 } 314 if (mQSCustomizerController != null) { 315 mQSCustomizerController.setQs(null); 316 mQSCustomizerController.setContainerController(null); 317 } 318 mScrollListener = null; 319 if (mContainer != null) { 320 mDumpManager.unregisterDumpable(mContainer.getClass().getSimpleName()); 321 } 322 mDumpManager.unregisterDumpable(getClass().getSimpleName()); 323 mListeningAndVisibilityLifecycleOwner.destroy(); 324 ViewGroup parent = ((ViewGroup) getView().getParent()); 325 if (parent != null) { 326 parent.removeView(getView()); 327 } 328 } 329 onSaveInstanceState(Bundle outState)330 public void onSaveInstanceState(Bundle outState) { 331 outState.putBoolean(EXTRA_EXPANDED, mQsExpanded); 332 outState.putBoolean(EXTRA_LISTENING, mListening); 333 outState.putBoolean(EXTRA_VISIBLE, mQsVisible); 334 if (mQSCustomizerController != null) { 335 mQSCustomizerController.saveInstanceState(outState); 336 } 337 if (mQsExpanded) { 338 mQSPanelController.getTileLayout().saveInstanceState(outState); 339 } 340 } 341 342 @VisibleForTesting isListening()343 boolean isListening() { 344 return mListening; 345 } 346 347 @VisibleForTesting isExpanded()348 boolean isExpanded() { 349 return mQsExpanded; 350 } 351 352 @VisibleForTesting isQsVisible()353 boolean isQsVisible() { 354 return mQsVisible; 355 } 356 357 @Override getHeader()358 public View getHeader() { 359 QSComposeFragment.assertInLegacyMode(); 360 return mHeader; 361 } 362 363 @Override getHeaderTop()364 public int getHeaderTop() { 365 return mHeader.getTop(); 366 } 367 368 @Override getHeaderBottom()369 public int getHeaderBottom() { 370 return mHeader.getBottom(); 371 } 372 373 @Override getHeaderLeft()374 public int getHeaderLeft() { 375 return mHeader.getLeft(); 376 } 377 378 @Override getHeaderBoundsOnScreen(Rect outBounds)379 public void getHeaderBoundsOnScreen(Rect outBounds) { 380 mHeader.getBoundsOnScreen(outBounds); 381 } 382 383 @Override isHeaderShown()384 public boolean isHeaderShown() { 385 return mHeader.isShown(); 386 } 387 388 @Override setHasNotifications(boolean hasNotifications)389 public void setHasNotifications(boolean hasNotifications) { 390 } 391 392 @Override setPanelView(HeightListener panelView)393 public void setPanelView(HeightListener panelView) { 394 mPanelView = panelView; 395 } 396 onConfigurationChanged(Configuration newConfig)397 public void onConfigurationChanged(Configuration newConfig) { 398 setEditLocation(getView()); 399 if (newConfig.getLayoutDirection() != mLayoutDirection) { 400 mLayoutDirection = newConfig.getLayoutDirection(); 401 if (mQSAnimator != null) { 402 mQSAnimator.onRtlChanged(); 403 } 404 } 405 updateQsState(); 406 } 407 408 @Override setFancyClipping(int leftInset, int top, int rightInset, int bottom, int cornerRadius, boolean visible, boolean fullWidth)409 public void setFancyClipping(int leftInset, int top, int rightInset, int bottom, 410 int cornerRadius, boolean visible, boolean fullWidth) { 411 if (getView() instanceof QSContainerImpl) { 412 ((QSContainerImpl) getView()).setFancyClipping(leftInset, top, rightInset, bottom, 413 cornerRadius, visible, fullWidth); 414 } 415 } 416 417 @Override isFullyCollapsed()418 public boolean isFullyCollapsed() { 419 return mLastQSExpansion == 0.0f || mLastQSExpansion == -1; 420 } 421 422 @Override setCollapsedMediaVisibilityChangedListener(Consumer<Boolean> listener)423 public void setCollapsedMediaVisibilityChangedListener(Consumer<Boolean> listener) { 424 mQuickQSPanelController.setMediaVisibilityChangedListener(listener); 425 } 426 setEditLocation(View view)427 private void setEditLocation(View view) { 428 View edit = view.findViewById(android.R.id.edit); 429 int[] loc = edit.getLocationOnScreen(); 430 int x = loc[0] + edit.getWidth() / 2; 431 int y = loc[1] + edit.getHeight() / 2; 432 mQSCustomizerController.setEditLocation(x, y); 433 } 434 435 @Override setContainerController(QSContainerController controller)436 public void setContainerController(QSContainerController controller) { 437 mQSCustomizerController.setContainerController(controller); 438 } 439 440 @Override isCustomizing()441 public boolean isCustomizing() { 442 return mQSCustomizerController.isCustomizing(); 443 } 444 445 @Override disable(int displayId, int state1, int state2, boolean animate)446 public void disable(int displayId, int state1, int state2, boolean animate) { 447 if (displayId != getContext().getDisplayId()) { 448 return; 449 } 450 int state2BeforeAdjustment = state2; 451 state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2); 452 453 mQsDisableFlagsLogger.logDisableFlagChange( 454 /* new= */ new DisableFlagsLogger.DisableState(state1, state2BeforeAdjustment), 455 /* newAfterLocalModification= */ new DisableFlagsLogger.DisableState(state1, state2) 456 ); 457 458 final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0; 459 if (disabled == mQsDisabled) return; 460 mQsDisabled = disabled; 461 mContainer.disable(state1, state2, animate); 462 mHeader.disable(state1, state2, animate); 463 mFooter.disable(state1, state2, animate); 464 updateQsState(); 465 } 466 updateQsState()467 private void updateQsState() { 468 final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling 469 || mHeaderAnimating; 470 mQSPanelController.setExpanded(mQsExpanded); 471 boolean keyguardShowing = isKeyguardState(); 472 mHeader.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating 473 || mShowCollapsedOnKeyguard) 474 ? View.VISIBLE 475 : View.INVISIBLE); 476 mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) 477 || (mQsExpanded && !mStackScrollerOverscrolling), mQuickQSPanelController); 478 boolean qsPanelVisible = !mQsDisabled && expandVisually; 479 boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing 480 || mHeaderAnimating || mShowCollapsedOnKeyguard); 481 mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE); 482 if (mFooterActionsView != null) { 483 mFooterActionsView.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE); 484 } 485 mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) 486 || (mQsExpanded && !mStackScrollerOverscrolling)); 487 mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE); 488 if (DEBUG) { 489 Log.d(TAG, "Footer: " + footerVisible + ", QS Panel: " + qsPanelVisible); 490 } 491 } 492 493 @VisibleForTesting isKeyguardState()494 boolean isKeyguardState() { 495 if (SceneContainerFlag.isEnabled()) { 496 return false; 497 } else { 498 // We want the freshest state here since otherwise we'll have some weirdness if earlier 499 // listeners trigger updates 500 return mStatusBarStateController.getCurrentOrUpcomingState() == KEYGUARD; 501 } 502 } 503 504 @VisibleForTesting getStatusBarState()505 int getStatusBarState() { 506 return mStatusBarState; 507 } 508 updateShowCollapsedOnKeyguard()509 private void updateShowCollapsedOnKeyguard() { 510 boolean showCollapsed = mBypassController.getBypassEnabled() 511 || (mTransitioningToFullShade && !mInSplitShade); 512 if (showCollapsed != mShowCollapsedOnKeyguard) { 513 mShowCollapsedOnKeyguard = showCollapsed; 514 updateQsState(); 515 if (mQSAnimator != null) { 516 mQSAnimator.setShowCollapsedOnKeyguard(showCollapsed); 517 } 518 if (!showCollapsed && isKeyguardState()) { 519 setQsExpansion(mLastQSExpansion, mLastPanelFraction, 0, 520 mSquishinessFraction); 521 } 522 } 523 } 524 getQSPanelController()525 public QSPanelController getQSPanelController() { 526 return mQSPanelController; 527 } 528 setBrightnessMirrorController( @ullable MirrorController brightnessMirrorController)529 public void setBrightnessMirrorController( 530 @Nullable MirrorController brightnessMirrorController) { 531 mQSPanelController.setBrightnessMirror(brightnessMirrorController); 532 } 533 534 @Override isShowingDetail()535 public boolean isShowingDetail() { 536 return mQSCustomizerController.isCustomizing(); 537 } 538 539 @Override setHeaderClickable(boolean clickable)540 public void setHeaderClickable(boolean clickable) { 541 if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable); 542 } 543 544 @Override setExpanded(boolean expanded)545 public void setExpanded(boolean expanded) { 546 if (DEBUG) Log.d(TAG, "setExpanded " + expanded); 547 mQsExpanded = expanded; 548 if (mInSplitShade && mQsExpanded) { 549 // in split shade QS is expanded immediately when shade expansion starts and then we 550 // also need to listen to changes - otherwise QS is updated only once its fully expanded 551 setListening(true); 552 } else { 553 updateQsPanelControllerListening(); 554 } 555 updateQsState(); 556 } 557 setKeyguardShowing(boolean keyguardShowing)558 private void setKeyguardShowing(boolean keyguardShowing) { 559 if (!SceneContainerFlag.isEnabled()) { 560 if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing); 561 mLastQSExpansion = -1; 562 563 if (mQSAnimator != null) { 564 mQSAnimator.setOnKeyguard(keyguardShowing); 565 } 566 567 mFooter.setKeyguardShowing(keyguardShowing); 568 updateQsState(); 569 } 570 } 571 572 @Override setOverscrolling(boolean stackScrollerOverscrolling)573 public void setOverscrolling(boolean stackScrollerOverscrolling) { 574 if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling); 575 mStackScrollerOverscrolling = stackScrollerOverscrolling; 576 updateQsState(); 577 } 578 579 @Override setListening(boolean listening)580 public void setListening(boolean listening) { 581 if (DEBUG) Log.d(TAG, "setListening " + listening); 582 mListening = listening; 583 mQSContainerImplController.setListening(listening && mQsVisible); 584 mListeningAndVisibilityLifecycleOwner.updateState(); 585 updateQsPanelControllerListening(); 586 } 587 updateQsPanelControllerListening()588 private void updateQsPanelControllerListening() { 589 mQSPanelController.setListening(mListening && mQsVisible, mQsExpanded); 590 } 591 592 @Override setQsVisible(boolean visible)593 public void setQsVisible(boolean visible) { 594 if (DEBUG) Log.d(TAG, "setQsVisible " + visible); 595 mQsVisible = visible; 596 setListening(mListening); 597 mListeningAndVisibilityLifecycleOwner.updateState(); 598 } 599 600 @Override setHeaderListening(boolean listening)601 public void setHeaderListening(boolean listening) { 602 mQSContainerImplController.setListening(listening); 603 } 604 605 @Override setInSplitShade(boolean inSplitShade)606 public void setInSplitShade(boolean inSplitShade) { 607 mInSplitShade = inSplitShade; 608 updateShowCollapsedOnKeyguard(); 609 updateQsState(); 610 } 611 612 @Override setTransitionToFullShadeProgress( boolean isTransitioningToFullShade, @FloatRange(from = 0.0, to = 1.0) float qsTransitionFraction, @FloatRange(from = 0.0, to = 1.0) float qsSquishinessFraction)613 public void setTransitionToFullShadeProgress( 614 boolean isTransitioningToFullShade, 615 @FloatRange(from = 0.0, to = 1.0) float qsTransitionFraction, 616 @FloatRange(from = 0.0, to = 1.0) float qsSquishinessFraction) { 617 if (isTransitioningToFullShade != mTransitioningToFullShade) { 618 mTransitioningToFullShade = isTransitioningToFullShade; 619 updateShowCollapsedOnKeyguard(); 620 } 621 mLockscreenToShadeProgress = qsTransitionFraction; 622 setQsExpansion(mLastQSExpansion, mLastPanelFraction, mLastHeaderTranslation, 623 isTransitioningToFullShade ? qsSquishinessFraction : mSquishinessFraction); 624 } 625 626 @Override setOverScrollAmount(int overScrollAmount)627 public void setOverScrollAmount(int overScrollAmount) { 628 mOverScrolling = overScrollAmount != 0; 629 View view = getView(); 630 if (view != null) { 631 view.setTranslationY(overScrollAmount); 632 } 633 } 634 635 @Override getHeightDiff()636 public int getHeightDiff() { 637 if (SceneContainerFlag.isEnabled()) { 638 return mQSPanelController.getViewBottom() - mHeader.getBottom() 639 + mHeader.getPaddingBottom(); 640 } else { 641 return mQSPanelScrollView.getBottom() - mHeader.getBottom() 642 + mHeader.getPaddingBottom(); 643 } 644 } 645 646 @Override setIsNotificationPanelFullWidth(boolean isFullWidth)647 public void setIsNotificationPanelFullWidth(boolean isFullWidth) { 648 mIsSmallScreen = isFullWidth; 649 } 650 651 @Override setShouldUpdateSquishinessOnMedia(boolean shouldUpdate)652 public void setShouldUpdateSquishinessOnMedia(boolean shouldUpdate) { 653 if (DEBUG) Log.d(TAG, "setShouldUpdateSquishinessOnMedia " + shouldUpdate); 654 mShouldUpdateMediaSquishiness = shouldUpdate; 655 } 656 657 @Override setQsExpansion(float expansion, float panelExpansionFraction, float proposedTranslation, float squishinessFraction)658 public void setQsExpansion(float expansion, float panelExpansionFraction, 659 float proposedTranslation, float squishinessFraction) { 660 float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation; 661 float alphaProgress = calculateAlphaProgress(panelExpansionFraction); 662 setAlphaAnimationProgress(alphaProgress); 663 mContainer.setExpansion(expansion); 664 final float translationScaleY = (mInSplitShade 665 ? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1); 666 boolean onKeyguard = isKeyguardState(); 667 boolean onKeyguardAndExpanded = onKeyguard && !mShowCollapsedOnKeyguard; 668 if (!mHeaderAnimating && !headerWillBeAnimating() && !mOverScrolling) { 669 getView().setTranslationY( 670 onKeyguardAndExpanded 671 ? translationScaleY * mHeader.getHeight() 672 : headerTranslation); 673 } 674 int currentHeight = getView().getHeight(); 675 if (expansion == mLastQSExpansion 676 && mLastKeyguardAndExpanded == onKeyguardAndExpanded 677 && mLastViewHeight == currentHeight 678 && mLastHeaderTranslation == headerTranslation 679 && mSquishinessFraction == squishinessFraction 680 && mLastPanelFraction == panelExpansionFraction) { 681 return; 682 } 683 mLastHeaderTranslation = headerTranslation; 684 mLastPanelFraction = panelExpansionFraction; 685 mSquishinessFraction = squishinessFraction; 686 mLastQSExpansion = expansion; 687 mLastKeyguardAndExpanded = onKeyguardAndExpanded; 688 mLastViewHeight = currentHeight; 689 690 boolean fullyExpanded = expansion == 1; 691 boolean fullyCollapsed = expansion == 0.0f; 692 int heightDiff = getHeightDiff(); 693 float panelTranslationY = translationScaleY * heightDiff; 694 695 if (expansion < 1 && expansion > 0.99) { 696 if (mQuickQSPanelController.switchTileLayout(false)) { 697 mHeader.updateResources(); 698 } 699 } 700 mQSPanelController.setIsOnKeyguard(onKeyguard); 701 mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion); 702 float footerActionsExpansion = 703 onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion; 704 if (mQSFooterActionsViewModel != null) { 705 mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion, 706 mInSplitShade); 707 } 708 mQSPanelController.setRevealExpansion(expansion); 709 mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); 710 mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); 711 712 if (!SceneContainerFlag.isEnabled()) { 713 float qsScrollViewTranslation = 714 onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0; 715 mQSPanelScrollView.setTranslationY(qsScrollViewTranslation); 716 717 if (fullyCollapsed) { 718 mQSPanelScrollView.setScrollY(0); 719 } 720 721 if (!fullyExpanded) { 722 // Set bounds on the QS panel so it doesn't run over the header when animating. 723 mQsBounds.top = (int) -mQSPanelScrollView.getTranslationY(); 724 mQsBounds.right = mQSPanelScrollView.getWidth(); 725 mQsBounds.bottom = mQSPanelScrollView.getHeight(); 726 } 727 } 728 updateQsBounds(); 729 730 if (mQSSquishinessController != null) { 731 mQSSquishinessController.setSquishiness(mSquishinessFraction); 732 } 733 if (mQSAnimator != null) { 734 mQSAnimator.setPosition(expansion); 735 } 736 if (!mShouldUpdateMediaSquishiness 737 && (!mInSplitShade 738 || mStatusBarStateController.getState() == KEYGUARD 739 || mStatusBarStateController.getState() == SHADE_LOCKED) 740 ) { 741 // At beginning, state is 0 and will apply wrong squishiness to MediaHost in lockscreen 742 // and media player expect no change by squishiness in lock screen shade. Don't bother 743 // squishing mQsMediaHost when not in split shade to prevent problems with stale state. 744 mQsMediaHost.setSquishFraction(1.0F); 745 } else { 746 mQsMediaHost.setSquishFraction(mSquishinessFraction); 747 } 748 updateMediaPositions(); 749 } 750 setAlphaAnimationProgress(float progress)751 private void setAlphaAnimationProgress(float progress) { 752 final View view = getView(); 753 if (progress == 0 && view.getVisibility() != View.INVISIBLE) { 754 mLogger.logVisibility("QS fragment", View.INVISIBLE); 755 view.setVisibility(View.INVISIBLE); 756 } else if (progress > 0 && view.getVisibility() != View.VISIBLE) { 757 mLogger.logVisibility("QS fragment", View.VISIBLE); 758 view.setVisibility((View.VISIBLE)); 759 } 760 view.setAlpha(interpolateAlphaAnimationProgress(progress)); 761 } 762 calculateAlphaProgress(float panelExpansionFraction)763 private float calculateAlphaProgress(float panelExpansionFraction) { 764 if (mIsSmallScreen) { 765 // Small screens. QS alpha is not animated. 766 return 1; 767 } 768 if (mInSplitShade) { 769 // Large screens in landscape. 770 // Need to check upcoming state as for unlocked -> AOD transition current state is 771 // not updated yet, but we're transitioning and UI should already follow KEYGUARD state 772 if (mTransitioningToFullShade 773 || mStatusBarStateController.getCurrentOrUpcomingState() == KEYGUARD) { 774 // Always use "mFullShadeProgress" on keyguard, because 775 // "panelExpansionFractions" is always 1 on keyguard split shade. 776 return mLockscreenToShadeProgress; 777 } else { 778 return panelExpansionFraction; 779 } 780 } 781 // Large screens in portrait. 782 if (mTransitioningToFullShade) { 783 // Only use this value during the standard lock screen shade expansion. During the 784 // "quick" expansion from top, this value is 0. 785 return mLockscreenToShadeProgress; 786 } else { 787 return panelExpansionFraction; 788 } 789 } 790 interpolateAlphaAnimationProgress(float progress)791 private float interpolateAlphaAnimationProgress(float progress) { 792 if (mQSPanelController.isBouncerInTransit()) { 793 return BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(progress); 794 } 795 if (isKeyguardState()) { 796 // Alpha progress should be linear on lockscreen shade expansion. 797 return progress; 798 } 799 if (mIsSmallScreen) { 800 return ShadeInterpolation.getContentAlpha(progress); 801 } else { 802 return mLargeScreenShadeInterpolator.getQsAlpha(progress); 803 } 804 } 805 806 @VisibleForTesting updateQsBounds()807 void updateQsBounds() { 808 if (mLastQSExpansion == 1.0f) { 809 // Fully expanded, let's set the layout bounds as clip bounds. This is necessary because 810 // it's a scrollview and otherwise wouldn't be clipped. However, we set the horizontal 811 // bounds so the pages go to the ends of QSContainerImpl (most cases) or its parent 812 // (large screen portrait) 813 int sideMargin = getResources().getDimensionPixelSize( 814 R.dimen.qs_tiles_page_horizontal_margin) * 2; 815 mQsBounds.set(-sideMargin, 0, mQSPanelScrollView.getWidth() + sideMargin, 816 mQSPanelScrollView.getHeight()); 817 } 818 if (!SceneContainerFlag.isEnabled()) { 819 mQSPanelScrollView.setClipBounds(mQsBounds); 820 821 mQSPanelScrollView.getLocationOnScreen(mLocationTemp); 822 int left = mLocationTemp[0]; 823 int top = mLocationTemp[1]; 824 mQsMediaHost.getCurrentClipping().set(left, top, 825 left + getView().getMeasuredWidth(), 826 top + mQSPanelScrollView.getMeasuredHeight() 827 - mQSPanelController.getPaddingBottom()); 828 } 829 } 830 updateMediaPositions()831 private void updateMediaPositions() { 832 if (Utils.useQsMediaPlayer(getContext())) { 833 View hostView = mQsMediaHost.getHostView(); 834 // Make sure the media appears a bit from the top to make it look nicer 835 if (mLastQSExpansion > 0 && !isKeyguardState() && !mQqsMediaHost.getVisible() 836 && !mQSPanelController.shouldUseHorizontalLayout() && !mInSplitShade) { 837 float interpolation = 1.0f - mLastQSExpansion; 838 interpolation = Interpolators.ACCELERATE.getInterpolation(interpolation); 839 float translationY = -hostView.getHeight() * 1.3f * interpolation; 840 hostView.setTranslationY(translationY); 841 } else { 842 hostView.setTranslationY(0); 843 } 844 } 845 } 846 headerWillBeAnimating()847 private boolean headerWillBeAnimating() { 848 return mStatusBarState == KEYGUARD && mShowCollapsedOnKeyguard && !isKeyguardState(); 849 } 850 851 @Override animateHeaderSlidingOut()852 public void animateHeaderSlidingOut() { 853 if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut"); 854 if (getView().getY() == -mHeader.getHeight()) { 855 return; 856 } 857 mHeaderAnimating = true; 858 getView().animate().y(-mHeader.getHeight()) 859 .setStartDelay(0) 860 .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD) 861 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 862 .setListener(new AnimatorListenerAdapter() { 863 @Override 864 public void onAnimationEnd(Animator animation) { 865 if (getView() != null) { 866 // The view could be destroyed before the animation completes when 867 // switching users. 868 getView().animate().setListener(null); 869 } 870 mHeaderAnimating = false; 871 updateQsState(); 872 } 873 }) 874 .start(); 875 } 876 877 @Override setCollapseExpandAction(Runnable action)878 public void setCollapseExpandAction(Runnable action) { 879 mQSPanelController.setCollapseExpandAction(action); 880 mQuickQSPanelController.setCollapseExpandAction(action); 881 } 882 883 @Override closeDetail()884 public void closeDetail() { 885 mQSPanelController.closeDetail(); 886 } 887 888 @Override closeCustomizer()889 public void closeCustomizer() { 890 mQSCustomizerController.hide(); 891 } 892 closeCustomizerImmediately()893 public void closeCustomizerImmediately() { 894 mQSCustomizerController.hide(false); 895 } 896 notifyCustomizeChanged()897 public void notifyCustomizeChanged() { 898 // The customize state changed, so our height changed. 899 mContainer.updateExpansion(); 900 boolean customizing = isCustomizing(); 901 if (SceneContainerFlag.isEnabled()) { 902 mQSPanelController.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); 903 } else { 904 mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); 905 } 906 mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); 907 if (mFooterActionsView != null) { 908 mFooterActionsView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); 909 } 910 mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); 911 // Let the panel know the position changed and it needs to update where notifications 912 // and whatnot are. 913 if (mPanelView != null) { 914 mPanelView.onQsHeightChanged(); 915 } 916 } 917 918 /** 919 * The height this view wants to be. This is different from {@link View#getMeasuredHeight} such 920 * that during closing the detail panel, this already returns the smaller height. 921 */ 922 @Override getDesiredHeight()923 public int getDesiredHeight() { 924 if (mQSCustomizerController.isCustomizing()) { 925 return getView().getHeight(); 926 } 927 return getView().getMeasuredHeight(); 928 } 929 930 @Override setHeightOverride(int desiredHeight)931 public void setHeightOverride(int desiredHeight) { 932 mContainer.setHeightOverride(desiredHeight); 933 } 934 935 @Override getQsMinExpansionHeight()936 public int getQsMinExpansionHeight() { 937 if (mInSplitShade) { 938 return getQsMinExpansionHeightForSplitShade(); 939 } 940 return mHeader.getHeight(); 941 } 942 943 /** 944 * Returns the min expansion height for split shade. 945 * 946 * On split shade, QS is always expanded and goes from the top of the screen to the bottom of 947 * the QS container. 948 */ getQsMinExpansionHeightForSplitShade()949 private int getQsMinExpansionHeightForSplitShade() { 950 getView().getLocationOnScreen(mLocationTemp); 951 int top = mLocationTemp[1]; 952 // We want to get the original top position, so we subtract any translation currently set. 953 int originalTop = (int) (top - getView().getTranslationY()); 954 // On split shade the QS view doesn't start at the top of the screen, so we need to add the 955 // top margin. 956 return originalTop + getView().getHeight(); 957 } 958 959 @Override hideImmediately()960 public void hideImmediately() { 961 getView().animate().cancel(); 962 getView().setY(-getQsMinExpansionHeight()); 963 } 964 965 @Override onUpcomingStateChanged(int upcomingState)966 public void onUpcomingStateChanged(int upcomingState) { 967 if (upcomingState == KEYGUARD) { 968 // refresh state of QS as soon as possible - while it's still upcoming - so in case of 969 // transition to KEYGUARD (e.g. from unlocked to AOD) all objects are aware they should 970 // already behave like on keyguard. Otherwise we might be doing extra work, 971 // e.g. QSAnimator making QS visible and then quickly invisible 972 onStateChanged(upcomingState); 973 } 974 } 975 976 @Override onStateChanged(int newState)977 public void onStateChanged(int newState) { 978 if (SceneContainerFlag.isEnabled() || newState == mStatusBarState) { 979 return; 980 } 981 mStatusBarState = newState; 982 setKeyguardShowing(newState == KEYGUARD); 983 updateShowCollapsedOnKeyguard(); 984 } 985 986 @VisibleForTesting getListeningAndVisibilityLifecycleOwner()987 public ListeningAndVisibilityLifecycleOwner getListeningAndVisibilityLifecycleOwner() { 988 return mListeningAndVisibilityLifecycleOwner; 989 } 990 getQQSHeight()991 public int getQQSHeight() { 992 return mContainer.getQqsHeight(); 993 } 994 995 /** 996 * @return height with the squishiness fraction applied. 997 */ getSquishedQqsHeight()998 public int getSquishedQqsHeight() { 999 return mContainer.getSquishedQqsHeight(); 1000 } 1001 getQSHeight()1002 public int getQSHeight() { 1003 return mContainer.getQsHeight(); 1004 } 1005 1006 /** 1007 * @return height with the squishiness fraction applied. 1008 */ getSquishedQsHeight()1009 public int getSquishedQsHeight() { 1010 return mContainer.getSquishedQsHeight(); 1011 } 1012 1013 /** 1014 * Pass the size of the navbar when it's at the bottom of the device so it can be used as 1015 * padding 1016 * @param padding size of the bottom nav bar in px 1017 */ applyBottomNavBarToCustomizerPadding(int padding)1018 public void applyBottomNavBarToCustomizerPadding(int padding) { 1019 mQSCustomizerController.applyBottomNavBarSizeToRecyclerViewPadding(padding); 1020 } 1021 1022 @NeverCompile 1023 @Override dump(PrintWriter pw, String[] args)1024 public void dump(PrintWriter pw, String[] args) { 1025 IndentingPrintWriter indentingPw = new IndentingPrintWriter(pw, /* singleIndent= */ " "); 1026 indentingPw.println("QSImpl:"); 1027 indentingPw.increaseIndent(); 1028 indentingPw.println("mQsBounds: " + mQsBounds); 1029 indentingPw.println("mQsExpanded: " + mQsExpanded); 1030 indentingPw.println("mHeaderAnimating: " + mHeaderAnimating); 1031 indentingPw.println("mStackScrollerOverscrolling: " + mStackScrollerOverscrolling); 1032 indentingPw.println("mListening: " + mListening); 1033 indentingPw.println("mQsVisible: " + mQsVisible); 1034 indentingPw.println("mLayoutDirection: " + mLayoutDirection); 1035 indentingPw.println("mLastQSExpansion: " + mLastQSExpansion); 1036 indentingPw.println("mLastPanelFraction: " + mLastPanelFraction); 1037 indentingPw.println("mSquishinessFraction: " + mSquishinessFraction); 1038 indentingPw.println("mQsDisabled: " + mQsDisabled); 1039 indentingPw.println("mTemp: " + Arrays.toString(mLocationTemp)); 1040 indentingPw.println("mShowCollapsedOnKeyguard: " + mShowCollapsedOnKeyguard); 1041 indentingPw.println("mLastKeyguardAndExpanded: " + mLastKeyguardAndExpanded); 1042 indentingPw.println("mStatusBarState: " + StatusBarState.toString(mStatusBarState)); 1043 indentingPw.println("mTmpLocation: " + Arrays.toString(mTmpLocation)); 1044 indentingPw.println("mLastViewHeight: " + mLastViewHeight); 1045 indentingPw.println("mLastHeaderTranslation: " + mLastHeaderTranslation); 1046 indentingPw.println("mInSplitShade: " + mInSplitShade); 1047 indentingPw.println("mTransitioningToFullShade: " + mTransitioningToFullShade); 1048 indentingPw.println("mLockscreenToShadeProgress: " + mLockscreenToShadeProgress); 1049 indentingPw.println("mOverScrolling: " + mOverScrolling); 1050 indentingPw.println("mShouldUpdateMediaSquishiness: " + mShouldUpdateMediaSquishiness); 1051 indentingPw.println("isCustomizing: " + mQSCustomizerController.isCustomizing()); 1052 View view = getView(); 1053 if (view != null) { 1054 indentingPw.println("top: " + view.getTop()); 1055 indentingPw.println("y: " + view.getY()); 1056 indentingPw.println("translationY: " + view.getTranslationY()); 1057 indentingPw.println("alpha: " + view.getAlpha()); 1058 indentingPw.println("height: " + view.getHeight()); 1059 indentingPw.println("measuredHeight: " + view.getMeasuredHeight()); 1060 indentingPw.println("clipBounds: " + view.getClipBounds()); 1061 } else { 1062 indentingPw.println("getView(): null"); 1063 } 1064 QuickStatusBarHeader header = mHeader; 1065 if (header != null) { 1066 indentingPw.println("headerHeight: " + header.getHeight()); 1067 indentingPw.println("Header visibility: " + visibilityToString(header.getVisibility())); 1068 } else { 1069 indentingPw.println("mHeader: null"); 1070 } 1071 } 1072 visibilityToString(int visibility)1073 private static String visibilityToString(int visibility) { 1074 if (visibility == View.VISIBLE) { 1075 return "VISIBLE"; 1076 } 1077 if (visibility == View.INVISIBLE) { 1078 return "INVISIBLE"; 1079 } 1080 return "GONE"; 1081 } 1082 1083 @Override getView()1084 public View getView() { 1085 return mRootView; 1086 } 1087 1088 @Override getContext()1089 public Context getContext() { 1090 return mRootView.getContext(); 1091 } 1092 getResources()1093 private Resources getResources() { 1094 return getContext().getResources(); 1095 } 1096 1097 /** 1098 * A {@link LifecycleOwner} whose state is driven by the current state of this fragment: 1099 * 1100 * - DESTROYED when the fragment is destroyed. 1101 * - CREATED when mListening == mQsVisible == false. 1102 * - STARTED when mListening == true && mQsVisible == false. 1103 * - RESUMED when mListening == true && mQsVisible == true. 1104 */ 1105 @VisibleForTesting 1106 class ListeningAndVisibilityLifecycleOwner implements LifecycleOwner { 1107 private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); 1108 private boolean mDestroyed = false; 1109 1110 { updateState()1111 updateState(); 1112 } 1113 1114 @Override getLifecycle()1115 public Lifecycle getLifecycle() { 1116 return mLifecycleRegistry; 1117 } 1118 1119 /** 1120 * Update the state of the associated lifecycle. This should be called whenever 1121 * {@code mListening} or {@code mQsVisible} is changed. 1122 */ updateState()1123 public void updateState() { 1124 if (mDestroyed) { 1125 mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED); 1126 return; 1127 } 1128 1129 if (!mListening) { 1130 mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED); 1131 return; 1132 } 1133 1134 // mListening && !mQsVisible. 1135 if (!mQsVisible) { 1136 mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED); 1137 return; 1138 } 1139 1140 // mListening && mQsVisible. 1141 mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED); 1142 } 1143 destroy()1144 public void destroy() { 1145 mDestroyed = true; 1146 updateState(); 1147 } 1148 } 1149 }