NotificationPanelView.java revision cf5d3c98d1da0b1cfd4976a6962e7244490bdc8d
1/* 2 * Copyright (C) 2012 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 17package com.android.systemui.statusbar.phone; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.animation.PropertyValuesHolder; 23import android.animation.ValueAnimator; 24import android.content.Context; 25import android.content.res.Configuration; 26import android.graphics.Color; 27import android.graphics.drawable.ColorDrawable; 28import android.util.AttributeSet; 29import android.util.MathUtils; 30import android.view.MotionEvent; 31import android.view.VelocityTracker; 32import android.view.View; 33import android.view.ViewTreeObserver; 34import android.view.accessibility.AccessibilityEvent; 35import android.view.animation.AnimationUtils; 36import android.view.animation.Interpolator; 37import android.widget.FrameLayout; 38import android.widget.TextView; 39 40import com.android.keyguard.KeyguardStatusView; 41import com.android.systemui.R; 42import com.android.systemui.qs.QSPanel; 43import com.android.systemui.statusbar.ExpandableView; 44import com.android.systemui.statusbar.FlingAnimationUtils; 45import com.android.systemui.statusbar.GestureRecorder; 46import com.android.systemui.statusbar.KeyguardAffordanceView; 47import com.android.systemui.statusbar.StatusBarState; 48import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; 49import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 50import com.android.systemui.statusbar.stack.StackStateAnimator; 51 52public class NotificationPanelView extends PanelView implements 53 ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, 54 View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener, 55 KeyguardAffordanceHelper.Callback { 56 57 // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is 58 // changed. 59 private static final int CAP_HEIGHT = 1456; 60 private static final int FONT_HEIGHT = 2163; 61 62 private static final float HEADER_RUBBERBAND_FACTOR = 2.05f; 63 private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f; 64 65 private static final int DOZE_BACKGROUND_COLOR = 0xff000000; 66 private static final int TAG_KEY_ANIM = R.id.scrim; 67 private static final long DOZE_BACKGROUND_ANIM_DURATION = ScrimController.ANIMATION_DURATION; 68 69 private KeyguardAffordanceHelper mAfforanceHelper; 70 private StatusBarHeaderView mHeader; 71 private KeyguardUserSwitcher mKeyguardUserSwitcher; 72 private KeyguardStatusBarView mKeyguardStatusBar; 73 private View mQsContainer; 74 private QSPanel mQsPanel; 75 private KeyguardStatusView mKeyguardStatusView; 76 private ObservableScrollView mScrollView; 77 private TextView mClockView; 78 private View mReserveNotificationSpace; 79 private View mQsNavbarScrim; 80 private View mNotificationContainerParent; 81 private NotificationStackScrollLayout mNotificationStackScroller; 82 private int mNotificationTopPadding; 83 private boolean mAnimateNextTopPaddingChange; 84 85 private int mTrackingPointer; 86 private VelocityTracker mVelocityTracker; 87 private boolean mQsTracking; 88 89 /** 90 * Handles launching the secure camera properly even when other applications may be using the 91 * camera hardware. 92 */ 93 private SecureCameraLaunchManager mSecureCameraLaunchManager; 94 95 /** 96 * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and 97 * the expansion for quick settings. 98 */ 99 private boolean mConflictingQsExpansionGesture; 100 101 /** 102 * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't 103 * intercepted yet. 104 */ 105 private boolean mIntercepting; 106 private boolean mQsExpanded; 107 private boolean mQsExpandedWhenExpandingStarted; 108 private boolean mQsFullyExpanded; 109 private boolean mKeyguardShowing; 110 private boolean mDozing; 111 private int mStatusBarState; 112 private float mInitialHeightOnTouch; 113 private float mInitialTouchX; 114 private float mInitialTouchY; 115 private float mLastTouchX; 116 private float mLastTouchY; 117 private float mQsExpansionHeight; 118 private int mQsMinExpansionHeight; 119 private int mQsMaxExpansionHeight; 120 private int mQsPeekHeight; 121 private boolean mStackScrollerOverscrolling; 122 private boolean mQsExpansionFromOverscroll; 123 private float mLastOverscroll; 124 private boolean mQsExpansionEnabled = true; 125 private ValueAnimator mQsExpansionAnimator; 126 private FlingAnimationUtils mFlingAnimationUtils; 127 private int mStatusBarMinHeight; 128 private boolean mUnlockIconActive; 129 private int mNotificationsHeaderCollideDistance; 130 private int mUnlockMoveDistance; 131 private float mEmptyDragAmount; 132 133 private Interpolator mFastOutSlowInInterpolator; 134 private Interpolator mFastOutLinearInterpolator; 135 private ObjectAnimator mClockAnimator; 136 private int mClockAnimationTarget = -1; 137 private int mTopPaddingAdjustment; 138 private KeyguardClockPositionAlgorithm mClockPositionAlgorithm = 139 new KeyguardClockPositionAlgorithm(); 140 private KeyguardClockPositionAlgorithm.Result mClockPositionResult = 141 new KeyguardClockPositionAlgorithm.Result(); 142 private boolean mIsExpanding; 143 144 private boolean mBlockTouches; 145 private int mNotificationScrimWaitDistance; 146 private boolean mTwoFingerQsExpand; 147 private boolean mTwoFingerQsExpandPossible; 148 149 /** 150 * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still 151 * need to take this into account in our panel height calculation. 152 */ 153 private int mScrollYOverride = -1; 154 private boolean mQsAnimatorExpand; 155 private boolean mIsLaunchTransitionFinished; 156 private boolean mIsLaunchTransitionRunning; 157 private Runnable mLaunchAnimationEndRunnable; 158 private boolean mOnlyAffordanceInThisMotion; 159 private boolean mKeyguardStatusViewAnimating; 160 private boolean mHeaderAnimatingIn; 161 private ObjectAnimator mQsContainerAnimator; 162 163 private boolean mShadeEmpty; 164 165 private boolean mQsScrimEnabled = true; 166 private boolean mLastAnnouncementWasQuickSettings; 167 private boolean mQsTouchAboveFalsingThreshold; 168 private int mQsFalsingThreshold; 169 170 public NotificationPanelView(Context context, AttributeSet attrs) { 171 super(context, attrs); 172 } 173 174 public void setStatusBar(PhoneStatusBar bar) { 175 mStatusBar = bar; 176 } 177 178 @Override 179 protected void onFinishInflate() { 180 super.onFinishInflate(); 181 mHeader = (StatusBarHeaderView) findViewById(R.id.header); 182 mHeader.setOnClickListener(this); 183 mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header); 184 mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view); 185 mQsContainer = findViewById(R.id.quick_settings_container); 186 mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel); 187 mClockView = (TextView) findViewById(R.id.clock_view); 188 mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view); 189 mScrollView.setListener(this); 190 mScrollView.setFocusable(false); 191 mReserveNotificationSpace = findViewById(R.id.reserve_notification_space); 192 mNotificationContainerParent = findViewById(R.id.notification_container_parent); 193 mNotificationStackScroller = (NotificationStackScrollLayout) 194 findViewById(R.id.notification_stack_scroller); 195 mNotificationStackScroller.setOnHeightChangedListener(this); 196 mNotificationStackScroller.setOverscrollTopChangedListener(this); 197 mNotificationStackScroller.setScrollView(mScrollView); 198 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), 199 android.R.interpolator.fast_out_slow_in); 200 mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(), 201 android.R.interpolator.fast_out_linear_in); 202 mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); 203 mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim); 204 mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext()); 205 mSecureCameraLaunchManager = 206 new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea); 207 208 // recompute internal state when qspanel height changes 209 mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() { 210 @Override 211 public void onLayoutChange(View v, int left, int top, int right, 212 int bottom, int oldLeft, int oldTop, int oldRight, 213 int oldBottom) { 214 final int height = bottom - top; 215 final int oldHeight = oldBottom - oldTop; 216 if (height != oldHeight) { 217 onScrollChanged(); 218 } 219 } 220 }); 221 } 222 223 @Override 224 protected void loadDimens() { 225 super.loadDimens(); 226 mNotificationTopPadding = getResources().getDimensionPixelSize( 227 R.dimen.notifications_top_padding); 228 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f); 229 mStatusBarMinHeight = getResources().getDimensionPixelSize( 230 com.android.internal.R.dimen.status_bar_height); 231 mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height); 232 mNotificationsHeaderCollideDistance = 233 getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance); 234 mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance); 235 mClockPositionAlgorithm.loadDimens(getResources()); 236 mNotificationScrimWaitDistance = 237 getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance); 238 mQsFalsingThreshold = getResources().getDimensionPixelSize( 239 R.dimen.qs_falsing_threshold); 240 } 241 242 public void updateResources() { 243 int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width); 244 int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity); 245 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mHeader.getLayoutParams(); 246 if (lp.width != panelWidth) { 247 lp.width = panelWidth; 248 lp.gravity = panelGravity; 249 mHeader.setLayoutParams(lp); 250 mHeader.post(mUpdateHeader); 251 } 252 253 lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams(); 254 if (lp.width != panelWidth) { 255 lp.width = panelWidth; 256 lp.gravity = panelGravity; 257 mNotificationStackScroller.setLayoutParams(lp); 258 } 259 260 lp = (FrameLayout.LayoutParams) mScrollView.getLayoutParams(); 261 if (lp.width != panelWidth) { 262 lp.width = panelWidth; 263 lp.gravity = panelGravity; 264 mScrollView.setLayoutParams(lp); 265 } 266 } 267 268 @Override 269 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 270 super.onLayout(changed, left, top, right, bottom); 271 272 // Update Clock Pivot 273 mKeyguardStatusView.setPivotX(getWidth() / 2); 274 mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize()); 275 276 // Calculate quick setting heights. 277 mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight; 278 mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight(); 279 positionClockAndNotifications(); 280 if (mQsExpanded) { 281 if (mQsFullyExpanded) { 282 mQsExpansionHeight = mQsMaxExpansionHeight; 283 requestScrollerTopPaddingUpdate(false /* animate */); 284 } 285 } else { 286 setQsExpansion(mQsMinExpansionHeight + mLastOverscroll); 287 mNotificationStackScroller.setStackHeight(getExpandedHeight()); 288 updateHeader(); 289 } 290 mNotificationStackScroller.updateIsSmallScreen( 291 mHeader.getCollapsedHeight() + mQsPeekHeight); 292 } 293 294 @Override 295 public void onAttachedToWindow() { 296 mSecureCameraLaunchManager.create(); 297 } 298 299 @Override 300 public void onDetachedFromWindow() { 301 mSecureCameraLaunchManager.destroy(); 302 } 303 304 /** 305 * Positions the clock and notifications dynamically depending on how many notifications are 306 * showing. 307 */ 308 private void positionClockAndNotifications() { 309 boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending(); 310 int stackScrollerPadding; 311 if (mStatusBarState != StatusBarState.KEYGUARD) { 312 int bottom = mHeader.getCollapsedHeight(); 313 stackScrollerPadding = mStatusBarState == StatusBarState.SHADE 314 ? bottom + mQsPeekHeight + mNotificationTopPadding 315 : mKeyguardStatusBar.getHeight() + mNotificationTopPadding; 316 mTopPaddingAdjustment = 0; 317 } else { 318 mClockPositionAlgorithm.setup( 319 mStatusBar.getMaxKeyguardNotifications(), 320 getMaxPanelHeight(), 321 getExpandedHeight(), 322 mNotificationStackScroller.getNotGoneChildCount(), 323 getHeight(), 324 mKeyguardStatusView.getHeight(), 325 mEmptyDragAmount); 326 mClockPositionAlgorithm.run(mClockPositionResult); 327 if (animate || mClockAnimator != null) { 328 startClockAnimation(mClockPositionResult.clockY); 329 } else { 330 mKeyguardStatusView.setY(mClockPositionResult.clockY); 331 } 332 updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale); 333 stackScrollerPadding = mClockPositionResult.stackScrollerPadding; 334 mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment; 335 } 336 mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding); 337 requestScrollerTopPaddingUpdate(animate); 338 } 339 340 private void startClockAnimation(int y) { 341 if (mClockAnimationTarget == y) { 342 return; 343 } 344 mClockAnimationTarget = y; 345 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 346 @Override 347 public boolean onPreDraw() { 348 getViewTreeObserver().removeOnPreDrawListener(this); 349 if (mClockAnimator != null) { 350 mClockAnimator.removeAllListeners(); 351 mClockAnimator.cancel(); 352 } 353 mClockAnimator = ObjectAnimator 354 .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget); 355 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator); 356 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 357 mClockAnimator.addListener(new AnimatorListenerAdapter() { 358 @Override 359 public void onAnimationEnd(Animator animation) { 360 mClockAnimator = null; 361 mClockAnimationTarget = -1; 362 } 363 }); 364 mClockAnimator.start(); 365 return true; 366 } 367 }); 368 } 369 370 private void updateClock(float alpha, float scale) { 371 if (!mKeyguardStatusViewAnimating) { 372 mKeyguardStatusView.setAlpha(alpha); 373 } 374 mKeyguardStatusView.setScaleX(scale); 375 mKeyguardStatusView.setScaleY(scale); 376 } 377 378 public void animateToFullShade(long delay) { 379 mAnimateNextTopPaddingChange = true; 380 mNotificationStackScroller.goToFullShade(delay); 381 requestLayout(); 382 } 383 384 public void setQsExpansionEnabled(boolean qsExpansionEnabled) { 385 mQsExpansionEnabled = qsExpansionEnabled; 386 mHeader.setClickable(qsExpansionEnabled); 387 } 388 389 @Override 390 public void resetViews() { 391 mIsLaunchTransitionFinished = false; 392 mBlockTouches = false; 393 mUnlockIconActive = false; 394 mAfforanceHelper.reset(true); 395 closeQs(); 396 mStatusBar.dismissPopups(); 397 mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */, 398 true /* cancelAnimators */); 399 } 400 401 public void closeQs() { 402 cancelAnimation(); 403 setQsExpansion(mQsMinExpansionHeight); 404 } 405 406 public void animateCloseQs() { 407 if (mQsExpansionAnimator != null) { 408 if (!mQsAnimatorExpand) { 409 return; 410 } 411 float height = mQsExpansionHeight; 412 mQsExpansionAnimator.cancel(); 413 setQsExpansion(height); 414 } 415 flingSettings(0 /* vel */, false); 416 } 417 418 public void openQs() { 419 cancelAnimation(); 420 if (mQsExpansionEnabled) { 421 setQsExpansion(mQsMaxExpansionHeight); 422 } 423 } 424 425 @Override 426 public void fling(float vel, boolean expand) { 427 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); 428 if (gr != null) { 429 gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); 430 } 431 super.fling(vel, expand); 432 } 433 434 @Override 435 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 436 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 437 event.getText().add(getKeyguardOrLockScreenString()); 438 mLastAnnouncementWasQuickSettings = false; 439 return true; 440 } 441 442 return super.dispatchPopulateAccessibilityEvent(event); 443 } 444 445 @Override 446 public boolean onInterceptTouchEvent(MotionEvent event) { 447 if (mBlockTouches) { 448 return false; 449 } 450 resetDownStates(event); 451 int pointerIndex = event.findPointerIndex(mTrackingPointer); 452 if (pointerIndex < 0) { 453 pointerIndex = 0; 454 mTrackingPointer = event.getPointerId(pointerIndex); 455 } 456 final float x = event.getX(pointerIndex); 457 final float y = event.getY(pointerIndex); 458 459 switch (event.getActionMasked()) { 460 case MotionEvent.ACTION_DOWN: 461 mIntercepting = true; 462 mInitialTouchY = y; 463 mInitialTouchX = x; 464 initVelocityTracker(); 465 trackMovement(event); 466 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { 467 getParent().requestDisallowInterceptTouchEvent(true); 468 } 469 if (mQsExpansionAnimator != null) { 470 onQsExpansionStarted(); 471 mInitialHeightOnTouch = mQsExpansionHeight; 472 mQsTracking = true; 473 mIntercepting = false; 474 mNotificationStackScroller.removeLongPressCallback(); 475 } 476 break; 477 case MotionEvent.ACTION_POINTER_UP: 478 final int upPointer = event.getPointerId(event.getActionIndex()); 479 if (mTrackingPointer == upPointer) { 480 // gesture is ongoing, find a new pointer to track 481 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 482 mTrackingPointer = event.getPointerId(newIndex); 483 mInitialTouchX = event.getX(newIndex); 484 mInitialTouchY = event.getY(newIndex); 485 } 486 break; 487 488 case MotionEvent.ACTION_MOVE: 489 final float h = y - mInitialTouchY; 490 trackMovement(event); 491 if (mQsTracking) { 492 493 // Already tracking because onOverscrolled was called. We need to update here 494 // so we don't stop for a frame until the next touch event gets handled in 495 // onTouchEvent. 496 setQsExpansion(h + mInitialHeightOnTouch); 497 trackMovement(event); 498 mIntercepting = false; 499 return true; 500 } 501 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) 502 && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { 503 onQsExpansionStarted(); 504 mInitialHeightOnTouch = mQsExpansionHeight; 505 mInitialTouchY = y; 506 mInitialTouchX = x; 507 mQsTracking = true; 508 mIntercepting = false; 509 mNotificationStackScroller.removeLongPressCallback(); 510 return true; 511 } 512 break; 513 514 case MotionEvent.ACTION_CANCEL: 515 case MotionEvent.ACTION_UP: 516 trackMovement(event); 517 if (mQsTracking) { 518 flingQsWithCurrentVelocity(); 519 mQsTracking = false; 520 } 521 mIntercepting = false; 522 break; 523 } 524 525 // Allow closing the whole panel when in SHADE state. 526 if (mStatusBarState == StatusBarState.SHADE) { 527 return super.onInterceptTouchEvent(event); 528 } else { 529 return !mQsExpanded && super.onInterceptTouchEvent(event); 530 } 531 } 532 533 private void resetDownStates(MotionEvent event) { 534 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 535 mOnlyAffordanceInThisMotion = false; 536 mQsTouchAboveFalsingThreshold = mQsFullyExpanded; 537 } 538 } 539 540 @Override 541 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 542 543 // Block request when interacting with the scroll view so we can still intercept the 544 // scrolling when QS is expanded. 545 if (mScrollView.isHandlingTouchEvent()) { 546 return; 547 } 548 super.requestDisallowInterceptTouchEvent(disallowIntercept); 549 } 550 551 private void flingQsWithCurrentVelocity() { 552 float vel = getCurrentVelocity(); 553 flingSettings(vel, flingExpandsQs(vel)); 554 } 555 556 private boolean flingExpandsQs(float vel) { 557 if (isBelowFalsingThreshold()) { 558 return false; 559 } 560 if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { 561 return getQsExpansionFraction() > 0.5f; 562 } else { 563 return vel > 0; 564 } 565 } 566 567 private boolean isBelowFalsingThreshold() { 568 return !mQsTouchAboveFalsingThreshold && mStatusBarState == StatusBarState.KEYGUARD; 569 } 570 571 private float getQsExpansionFraction() { 572 return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight) 573 / (getTempQsMaxExpansion() - mQsMinExpansionHeight)); 574 } 575 576 @Override 577 public boolean onTouchEvent(MotionEvent event) { 578 if (mBlockTouches) { 579 return false; 580 } 581 resetDownStates(event); 582 if ((!mIsExpanding || mHintAnimationRunning) 583 && !mQsExpanded 584 && mStatusBar.getBarState() != StatusBarState.SHADE) { 585 mAfforanceHelper.onTouchEvent(event); 586 } 587 if (mOnlyAffordanceInThisMotion) { 588 return true; 589 } 590 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f 591 && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded 592 && mQsExpansionEnabled) { 593 594 // Down in the empty area while fully expanded - go to QS. 595 mQsTracking = true; 596 mConflictingQsExpansionGesture = true; 597 onQsExpansionStarted(); 598 mInitialHeightOnTouch = mQsExpansionHeight; 599 mInitialTouchY = event.getX(); 600 mInitialTouchX = event.getY(); 601 } 602 if (mExpandedHeight != 0) { 603 handleQsDown(event); 604 } 605 if (!mTwoFingerQsExpand && mQsTracking) { 606 onQsTouch(event); 607 if (!mConflictingQsExpansionGesture) { 608 return true; 609 } 610 } 611 if (event.getActionMasked() == MotionEvent.ACTION_CANCEL 612 || event.getActionMasked() == MotionEvent.ACTION_UP) { 613 mConflictingQsExpansionGesture = false; 614 } 615 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0 616 && mQsExpansionEnabled) { 617 mTwoFingerQsExpandPossible = true; 618 } 619 if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN 620 && event.getPointerCount() == 2 621 && event.getY(event.getActionIndex()) < mStatusBarMinHeight) { 622 mTwoFingerQsExpand = true; 623 requestPanelHeightUpdate(); 624 625 // Normally, we start listening when the panel is expanded, but here we need to start 626 // earlier so the state is already up to date when dragging down. 627 setListening(true); 628 } 629 super.onTouchEvent(event); 630 return true; 631 } 632 633 private boolean isInQsArea(float x, float y) { 634 return mStatusBarState != StatusBarState.SHADE || 635 (x >= mScrollView.getLeft() && x <= mScrollView.getRight()) && 636 (y <= mNotificationStackScroller.getBottomMostNotificationBottom() 637 || y <= mQsContainer.getY() + mQsContainer.getHeight()); 638 } 639 640 private void handleQsDown(MotionEvent event) { 641 if (event.getActionMasked() == MotionEvent.ACTION_DOWN 642 && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) { 643 mQsTracking = true; 644 onQsExpansionStarted(); 645 mInitialHeightOnTouch = mQsExpansionHeight; 646 mInitialTouchY = event.getX(); 647 mInitialTouchX = event.getY(); 648 649 // If we interrupt an expansion gesture here, make sure to update the state correctly. 650 if (mIsExpanding) { 651 onExpandingFinished(); 652 } 653 } 654 } 655 656 @Override 657 protected boolean flingExpands(float vel, float vectorVel) { 658 boolean expands = super.flingExpands(vel, vectorVel); 659 660 // If we are already running a QS expansion, make sure that we keep the panel open. 661 if (mQsExpansionAnimator != null) { 662 expands = true; 663 } 664 return expands; 665 } 666 667 @Override 668 protected boolean hasConflictingGestures() { 669 return mStatusBar.getBarState() != StatusBarState.SHADE; 670 } 671 672 private void onQsTouch(MotionEvent event) { 673 int pointerIndex = event.findPointerIndex(mTrackingPointer); 674 if (pointerIndex < 0) { 675 pointerIndex = 0; 676 mTrackingPointer = event.getPointerId(pointerIndex); 677 } 678 final float y = event.getY(pointerIndex); 679 final float x = event.getX(pointerIndex); 680 681 switch (event.getActionMasked()) { 682 case MotionEvent.ACTION_DOWN: 683 mQsTracking = true; 684 mInitialTouchY = y; 685 mInitialTouchX = x; 686 onQsExpansionStarted(); 687 mInitialHeightOnTouch = mQsExpansionHeight; 688 initVelocityTracker(); 689 trackMovement(event); 690 break; 691 692 case MotionEvent.ACTION_POINTER_UP: 693 final int upPointer = event.getPointerId(event.getActionIndex()); 694 if (mTrackingPointer == upPointer) { 695 // gesture is ongoing, find a new pointer to track 696 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 697 final float newY = event.getY(newIndex); 698 final float newX = event.getX(newIndex); 699 mTrackingPointer = event.getPointerId(newIndex); 700 mInitialHeightOnTouch = mQsExpansionHeight; 701 mInitialTouchY = newY; 702 mInitialTouchX = newX; 703 } 704 break; 705 706 case MotionEvent.ACTION_MOVE: 707 final float h = y - mInitialTouchY; 708 setQsExpansion(h + mInitialHeightOnTouch); 709 if (h >= mQsFalsingThreshold) { 710 mQsTouchAboveFalsingThreshold = true; 711 } 712 trackMovement(event); 713 break; 714 715 case MotionEvent.ACTION_UP: 716 case MotionEvent.ACTION_CANCEL: 717 mQsTracking = false; 718 mTrackingPointer = -1; 719 trackMovement(event); 720 float fraction = getQsExpansionFraction(); 721 if ((fraction != 0f || y >= mInitialTouchY) 722 && (fraction != 1f || y <= mInitialTouchY)) { 723 flingQsWithCurrentVelocity(); 724 } else { 725 mScrollYOverride = -1; 726 } 727 if (mVelocityTracker != null) { 728 mVelocityTracker.recycle(); 729 mVelocityTracker = null; 730 } 731 break; 732 } 733 } 734 735 @Override 736 public void onOverscrolled(float lastTouchX, float lastTouchY, int amount) { 737 if (mIntercepting && shouldQuickSettingsIntercept(lastTouchX, lastTouchY, 738 -1 /* yDiff: Not relevant here */)) { 739 onQsExpansionStarted(amount); 740 mInitialHeightOnTouch = mQsExpansionHeight; 741 mInitialTouchY = mLastTouchY; 742 mInitialTouchX = mLastTouchX; 743 mQsTracking = true; 744 } 745 } 746 747 @Override 748 public void onOverscrollTopChanged(float amount, boolean isRubberbanded) { 749 cancelAnimation(); 750 if (!mQsExpansionEnabled) { 751 amount = 0f; 752 } 753 float rounded = amount >= 1f ? amount : 0f; 754 mStackScrollerOverscrolling = rounded != 0f && isRubberbanded; 755 mQsExpansionFromOverscroll = rounded != 0f; 756 mLastOverscroll = rounded; 757 updateQsState(); 758 setQsExpansion(mQsMinExpansionHeight + rounded); 759 } 760 761 @Override 762 public void flingTopOverscroll(float velocity, boolean open) { 763 mLastOverscroll = 0f; 764 setQsExpansion(mQsExpansionHeight); 765 flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled, 766 new Runnable() { 767 @Override 768 public void run() { 769 mStackScrollerOverscrolling = false; 770 mQsExpansionFromOverscroll = false; 771 updateQsState(); 772 } 773 }); 774 } 775 776 private void onQsExpansionStarted() { 777 onQsExpansionStarted(0); 778 } 779 780 private void onQsExpansionStarted(int overscrollAmount) { 781 cancelAnimation(); 782 783 // Reset scroll position and apply that position to the expanded height. 784 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount; 785 if (mScrollView.getScrollY() != 0) { 786 mScrollYOverride = mScrollView.getScrollY(); 787 } 788 mScrollView.scrollTo(0, 0); 789 setQsExpansion(height); 790 } 791 792 private void setQsExpanded(boolean expanded) { 793 boolean changed = mQsExpanded != expanded; 794 if (changed) { 795 mQsExpanded = expanded; 796 updateQsState(); 797 requestPanelHeightUpdate(); 798 mNotificationStackScroller.setInterceptDelegateEnabled(expanded); 799 mStatusBar.setQsExpanded(expanded); 800 } 801 } 802 803 public void setBarState(int statusBarState, boolean keyguardFadingAway, 804 boolean goingToFullShade) { 805 boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD 806 || statusBarState == StatusBarState.SHADE_LOCKED; 807 if (!mKeyguardShowing && keyguardShowing) { 808 setQsTranslation(mQsExpansionHeight); 809 mHeader.setTranslationY(0f); 810 } 811 setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade); 812 setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade); 813 if (goingToFullShade) { 814 animateKeyguardStatusBarOut(); 815 } else { 816 mKeyguardStatusBar.setAlpha(1f); 817 mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE); 818 } 819 mStatusBarState = statusBarState; 820 mKeyguardShowing = keyguardShowing; 821 updateQsState(); 822 if (goingToFullShade) { 823 animateHeaderSlidingIn(); 824 } 825 } 826 827 private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() { 828 @Override 829 public void run() { 830 mKeyguardStatusViewAnimating = false; 831 mKeyguardStatusView.setVisibility(View.GONE); 832 } 833 }; 834 835 private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() { 836 @Override 837 public void run() { 838 mKeyguardStatusViewAnimating = false; 839 } 840 }; 841 842 private final Animator.AnimatorListener mAnimateHeaderSlidingInListener 843 = new AnimatorListenerAdapter() { 844 @Override 845 public void onAnimationEnd(Animator animation) { 846 mHeaderAnimatingIn = false; 847 mQsContainerAnimator = null; 848 mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater); 849 } 850 }; 851 852 private final OnLayoutChangeListener mQsContainerAnimatorUpdater 853 = new OnLayoutChangeListener() { 854 @Override 855 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 856 int oldTop, int oldRight, int oldBottom) { 857 int oldHeight = oldBottom - oldTop; 858 int height = bottom - top; 859 if (height != oldHeight && mQsContainerAnimator != null) { 860 PropertyValuesHolder[] values = mQsContainerAnimator.getValues(); 861 float newEndValue = mHeader.getCollapsedHeight() + mQsPeekHeight - height - top; 862 float newStartValue = -height - top; 863 values[0].setFloatValues(newStartValue, newEndValue); 864 mQsContainerAnimator.setCurrentPlayTime(mQsContainerAnimator.getCurrentPlayTime()); 865 } 866 } 867 }; 868 869 private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn 870 = new ViewTreeObserver.OnPreDrawListener() { 871 @Override 872 public boolean onPreDraw() { 873 getViewTreeObserver().removeOnPreDrawListener(this); 874 mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight); 875 mHeader.animate() 876 .translationY(0f) 877 .setStartDelay(mStatusBar.calculateGoingToFullShadeDelay()) 878 .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE) 879 .setInterpolator(mFastOutSlowInInterpolator) 880 .start(); 881 mQsContainer.setY(-mQsContainer.getHeight()); 882 mQsContainerAnimator = ObjectAnimator.ofFloat(mQsContainer, View.TRANSLATION_Y, 883 mQsContainer.getTranslationY(), 884 mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight() 885 - mQsContainer.getTop()); 886 mQsContainerAnimator.setStartDelay(mStatusBar.calculateGoingToFullShadeDelay()); 887 mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE); 888 mQsContainerAnimator.setInterpolator(mFastOutSlowInInterpolator); 889 mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener); 890 mQsContainerAnimator.start(); 891 mQsContainer.addOnLayoutChangeListener(mQsContainerAnimatorUpdater); 892 return true; 893 } 894 }; 895 896 private void animateHeaderSlidingIn() { 897 mHeaderAnimatingIn = true; 898 getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn); 899 900 } 901 902 private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() { 903 @Override 904 public void run() { 905 mKeyguardStatusBar.setVisibility(View.INVISIBLE); 906 } 907 }; 908 909 private void animateKeyguardStatusBarOut() { 910 mKeyguardStatusBar.animate() 911 .alpha(0f) 912 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) 913 .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) 914 .setInterpolator(PhoneStatusBar.ALPHA_OUT) 915 .withEndAction(mAnimateKeyguardStatusBarInvisibleEndRunnable) 916 .start(); 917 } 918 919 private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() { 920 @Override 921 public void run() { 922 mKeyguardBottomArea.setVisibility(View.GONE); 923 } 924 }; 925 926 private void setKeyguardBottomAreaVisibility(int statusBarState, 927 boolean goingToFullShade) { 928 if (goingToFullShade) { 929 mKeyguardBottomArea.animate().cancel(); 930 mKeyguardBottomArea.animate() 931 .alpha(0f) 932 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) 933 .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) 934 .setInterpolator(PhoneStatusBar.ALPHA_OUT) 935 .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable) 936 .start(); 937 } else if (statusBarState == StatusBarState.KEYGUARD 938 || statusBarState == StatusBarState.SHADE_LOCKED) { 939 mKeyguardBottomArea.animate().cancel(); 940 mKeyguardBottomArea.setVisibility(View.VISIBLE); 941 mKeyguardBottomArea.setAlpha(1f); 942 } else { 943 mKeyguardBottomArea.animate().cancel(); 944 mKeyguardBottomArea.setVisibility(View.GONE); 945 mKeyguardBottomArea.setAlpha(1f); 946 } 947 } 948 949 private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway, 950 boolean goingToFullShade) { 951 if ((!keyguardFadingAway && mStatusBarState == StatusBarState.KEYGUARD 952 && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) { 953 mKeyguardStatusView.animate().cancel(); 954 mKeyguardStatusViewAnimating = true; 955 mKeyguardStatusView.animate() 956 .alpha(0f) 957 .setStartDelay(0) 958 .setDuration(160) 959 .setInterpolator(PhoneStatusBar.ALPHA_OUT) 960 .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable); 961 if (keyguardFadingAway) { 962 mKeyguardStatusView.animate() 963 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) 964 .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) 965 .start(); 966 } 967 } else if (mStatusBarState == StatusBarState.SHADE_LOCKED 968 && statusBarState == StatusBarState.KEYGUARD) { 969 mKeyguardStatusView.animate().cancel(); 970 mKeyguardStatusView.setVisibility(View.VISIBLE); 971 mKeyguardStatusViewAnimating = true; 972 mKeyguardStatusView.setAlpha(0f); 973 mKeyguardStatusView.animate() 974 .alpha(1f) 975 .setStartDelay(0) 976 .setDuration(320) 977 .setInterpolator(PhoneStatusBar.ALPHA_IN) 978 .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable); 979 } else if (statusBarState == StatusBarState.KEYGUARD) { 980 mKeyguardStatusView.animate().cancel(); 981 mKeyguardStatusView.setVisibility(View.VISIBLE); 982 mKeyguardStatusView.setAlpha(1f); 983 } else { 984 mKeyguardStatusView.animate().cancel(); 985 mKeyguardStatusView.setVisibility(View.GONE); 986 mKeyguardStatusView.setAlpha(1f); 987 } 988 } 989 990 private void updateQsState() { 991 boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling; 992 mHeader.setVisibility((mQsExpanded || !mKeyguardShowing) ? View.VISIBLE : View.INVISIBLE); 993 mHeader.setExpanded(mKeyguardShowing || (mQsExpanded && !mStackScrollerOverscrolling)); 994 mNotificationStackScroller.setScrollingEnabled( 995 mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded 996 || mQsExpansionFromOverscroll)); 997 mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE); 998 mQsContainer.setVisibility( 999 mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE); 1000 mScrollView.setTouchEnabled(mQsExpanded); 1001 updateEmptyShadeView(); 1002 mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded 1003 && !mStackScrollerOverscrolling && mQsScrimEnabled 1004 ? View.VISIBLE 1005 : View.INVISIBLE); 1006 if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) { 1007 mKeyguardUserSwitcher.hide(true /* animate */); 1008 } 1009 } 1010 1011 private void setQsExpansion(float height) { 1012 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); 1013 mQsFullyExpanded = height == mQsMaxExpansionHeight; 1014 if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) { 1015 setQsExpanded(true); 1016 } else if (height <= mQsMinExpansionHeight && mQsExpanded) { 1017 setQsExpanded(false); 1018 if (mLastAnnouncementWasQuickSettings && !mTracking) { 1019 announceForAccessibility(getKeyguardOrLockScreenString()); 1020 mLastAnnouncementWasQuickSettings = false; 1021 } 1022 } 1023 mQsExpansionHeight = height; 1024 mHeader.setExpansion(getHeaderExpansionFraction()); 1025 setQsTranslation(height); 1026 requestScrollerTopPaddingUpdate(false /* animate */); 1027 updateNotificationScrim(height); 1028 if (mKeyguardShowing) { 1029 updateHeaderKeyguard(); 1030 } 1031 if (mStatusBarState == StatusBarState.SHADE && mQsExpanded 1032 && !mStackScrollerOverscrolling && mQsScrimEnabled) { 1033 mQsNavbarScrim.setAlpha(getQsExpansionFraction()); 1034 } 1035 1036 // Upon initialisation when we are not layouted yet we don't want to announce that we are 1037 // fully expanded, hence the != 0.0f check. 1038 if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) { 1039 announceForAccessibility(getContext().getString( 1040 R.string.accessibility_desc_quick_settings)); 1041 mLastAnnouncementWasQuickSettings = true; 1042 } 1043 } 1044 1045 private String getKeyguardOrLockScreenString() { 1046 if (mStatusBarState == StatusBarState.KEYGUARD) { 1047 return getContext().getString(R.string.accessibility_desc_lock_screen); 1048 } else { 1049 return getContext().getString(R.string.accessibility_desc_notification_shade); 1050 } 1051 } 1052 1053 private void updateNotificationScrim(float height) { 1054 int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance; 1055 float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance); 1056 progress = Math.max(0.0f, Math.min(progress, 1.0f)); 1057 } 1058 1059 private float getHeaderExpansionFraction() { 1060 if (!mKeyguardShowing) { 1061 return getQsExpansionFraction(); 1062 } else { 1063 return 1f; 1064 } 1065 } 1066 1067 private void setQsTranslation(float height) { 1068 if (!mHeaderAnimatingIn) { 1069 mQsContainer.setY(height - mQsContainer.getHeight() + getHeaderTranslation()); 1070 } 1071 if (mKeyguardShowing) { 1072 mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0)); 1073 } 1074 } 1075 1076 private float calculateQsTopPadding() { 1077 // We can only do the smoother transition on Keyguard when we also are not collapsing from a 1078 // scrolled quick settings. 1079 if (mKeyguardShowing && mScrollYOverride == -1) { 1080 return interpolate(getQsExpansionFraction(), 1081 mNotificationStackScroller.getIntrinsicPadding() - mNotificationTopPadding, 1082 mQsMaxExpansionHeight); 1083 } else { 1084 return mQsExpansionHeight; 1085 } 1086 } 1087 1088 private void requestScrollerTopPaddingUpdate(boolean animate) { 1089 mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), 1090 mScrollView.getScrollY(), 1091 mAnimateNextTopPaddingChange || animate); 1092 mAnimateNextTopPaddingChange = false; 1093 } 1094 1095 private void trackMovement(MotionEvent event) { 1096 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 1097 mLastTouchX = event.getX(); 1098 mLastTouchY = event.getY(); 1099 } 1100 1101 private void initVelocityTracker() { 1102 if (mVelocityTracker != null) { 1103 mVelocityTracker.recycle(); 1104 } 1105 mVelocityTracker = VelocityTracker.obtain(); 1106 } 1107 1108 private float getCurrentVelocity() { 1109 if (mVelocityTracker == null) { 1110 return 0; 1111 } 1112 mVelocityTracker.computeCurrentVelocity(1000); 1113 return mVelocityTracker.getYVelocity(); 1114 } 1115 1116 private void cancelAnimation() { 1117 if (mQsExpansionAnimator != null) { 1118 mQsExpansionAnimator.cancel(); 1119 } 1120 } 1121 1122 private void flingSettings(float vel, boolean expand) { 1123 flingSettings(vel, expand, null); 1124 } 1125 1126 private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable) { 1127 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight; 1128 if (target == mQsExpansionHeight) { 1129 mScrollYOverride = -1; 1130 if (onFinishRunnable != null) { 1131 onFinishRunnable.run(); 1132 } 1133 return; 1134 } 1135 boolean belowFalsingThreshold = isBelowFalsingThreshold(); 1136 if (belowFalsingThreshold) { 1137 vel = 0; 1138 } 1139 mScrollView.setBlockFlinging(true); 1140 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target); 1141 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel); 1142 if (belowFalsingThreshold) { 1143 animator.setDuration(350); 1144 } 1145 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 1146 @Override 1147 public void onAnimationUpdate(ValueAnimator animation) { 1148 setQsExpansion((Float) animation.getAnimatedValue()); 1149 } 1150 }); 1151 animator.addListener(new AnimatorListenerAdapter() { 1152 @Override 1153 public void onAnimationEnd(Animator animation) { 1154 mScrollView.setBlockFlinging(false); 1155 mScrollYOverride = -1; 1156 mQsExpansionAnimator = null; 1157 if (onFinishRunnable != null) { 1158 onFinishRunnable.run(); 1159 } 1160 } 1161 }); 1162 animator.start(); 1163 mQsExpansionAnimator = animator; 1164 mQsAnimatorExpand = expand; 1165 } 1166 1167 /** 1168 * @return Whether we should intercept a gesture to open Quick Settings. 1169 */ 1170 private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { 1171 if (!mQsExpansionEnabled) { 1172 return false; 1173 } 1174 View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader; 1175 boolean onHeader = x >= header.getLeft() && x <= header.getRight() 1176 && y >= header.getTop() && y <= header.getBottom(); 1177 if (mQsExpanded) { 1178 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y); 1179 } else { 1180 return onHeader; 1181 } 1182 } 1183 1184 @Override 1185 protected boolean isScrolledToBottom() { 1186 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1187 return true; 1188 } 1189 if (!isInSettings()) { 1190 return mNotificationStackScroller.isScrolledToBottom(); 1191 } else { 1192 return mScrollView.isScrolledToBottom(); 1193 } 1194 } 1195 1196 @Override 1197 protected int getMaxPanelHeight() { 1198 int min = mStatusBarMinHeight; 1199 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD 1200 && mNotificationStackScroller.getNotGoneChildCount() == 0) { 1201 int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount()) 1202 * HEADER_RUBBERBAND_FACTOR); 1203 min = Math.max(min, minHeight); 1204 } 1205 int maxHeight; 1206 if (mTwoFingerQsExpand || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) { 1207 maxHeight = Math.max(calculatePanelHeightQsExpanded(), calculatePanelHeightShade()); 1208 } else { 1209 maxHeight = calculatePanelHeightShade(); 1210 } 1211 maxHeight = Math.max(maxHeight, min); 1212 return maxHeight; 1213 } 1214 1215 private boolean isInSettings() { 1216 return mQsExpanded; 1217 } 1218 1219 @Override 1220 protected void onHeightUpdated(float expandedHeight) { 1221 if (!mQsExpanded) { 1222 positionClockAndNotifications(); 1223 } 1224 if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null 1225 && !mQsExpansionFromOverscroll) { 1226 float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding() 1227 + mNotificationStackScroller.getMinStackHeight() 1228 + mNotificationStackScroller.getNotificationTopPadding(); 1229 float panelHeightQsExpanded = calculatePanelHeightQsExpanded(); 1230 float t = (expandedHeight - panelHeightQsCollapsed) 1231 / (panelHeightQsExpanded - panelHeightQsCollapsed); 1232 1233 setQsExpansion(mQsMinExpansionHeight 1234 + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight)); 1235 } 1236 mNotificationStackScroller.setStackHeight(expandedHeight); 1237 updateHeader(); 1238 updateUnlockIcon(); 1239 updateNotificationTranslucency(); 1240 } 1241 1242 /** 1243 * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when 1244 * collapsing QS / the panel when QS was scrolled 1245 */ 1246 private int getTempQsMaxExpansion() { 1247 int qsTempMaxExpansion = mQsMaxExpansionHeight; 1248 if (mScrollYOverride != -1) { 1249 qsTempMaxExpansion -= mScrollYOverride; 1250 } 1251 return qsTempMaxExpansion; 1252 } 1253 1254 private int calculatePanelHeightShade() { 1255 int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin(); 1256 int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin 1257 - mTopPaddingAdjustment; 1258 maxHeight += mNotificationStackScroller.getTopPaddingOverflow(); 1259 return maxHeight; 1260 } 1261 1262 private int calculatePanelHeightQsExpanded() { 1263 float notificationHeight = mNotificationStackScroller.getHeight() 1264 - mNotificationStackScroller.getEmptyBottomMargin() 1265 - mNotificationStackScroller.getTopPadding(); 1266 float totalHeight = mQsMaxExpansionHeight + notificationHeight 1267 + mNotificationStackScroller.getNotificationTopPadding(); 1268 if (totalHeight > mNotificationStackScroller.getHeight()) { 1269 float fullyCollapsedHeight = mQsMaxExpansionHeight 1270 + mNotificationStackScroller.getMinStackHeight() 1271 + mNotificationStackScroller.getNotificationTopPadding() 1272 - getScrollViewScrollY(); 1273 totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight()); 1274 } 1275 return (int) totalHeight; 1276 } 1277 1278 private int getScrollViewScrollY() { 1279 if (mScrollYOverride != -1) { 1280 return mScrollYOverride; 1281 } else { 1282 return mScrollView.getScrollY(); 1283 } 1284 } 1285 private void updateNotificationTranslucency() { 1286 float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight()) 1287 / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() 1288 - mNotificationStackScroller.getCollapseSecondCardPadding()); 1289 alpha = Math.max(0, Math.min(alpha, 1)); 1290 alpha = (float) Math.pow(alpha, 0.75); 1291 if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) { 1292 mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null); 1293 } else if (alpha == 1f 1294 && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) { 1295 mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null); 1296 } 1297 mNotificationStackScroller.setAlpha(alpha); 1298 } 1299 1300 @Override 1301 protected float getOverExpansionAmount() { 1302 return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */); 1303 } 1304 1305 @Override 1306 protected float getOverExpansionPixels() { 1307 return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */); 1308 } 1309 1310 private void updateUnlockIcon() { 1311 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1312 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1313 boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance; 1314 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1315 if (active && !mUnlockIconActive && mTracking) { 1316 lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null); 1317 lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150, 1318 mFastOutLinearInterpolator); 1319 } else if (!active && mUnlockIconActive && mTracking) { 1320 lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true, 1321 150, mFastOutLinearInterpolator, null); 1322 lockIcon.setImageScale(1.0f, true, 150, 1323 mFastOutLinearInterpolator); 1324 } 1325 mUnlockIconActive = active; 1326 } 1327 } 1328 1329 /** 1330 * Hides the header when notifications are colliding with it. 1331 */ 1332 private void updateHeader() { 1333 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1334 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1335 updateHeaderKeyguard(); 1336 } else { 1337 updateHeaderShade(); 1338 } 1339 1340 } 1341 1342 private void updateHeaderShade() { 1343 if (!mHeaderAnimatingIn) { 1344 mHeader.setTranslationY(getHeaderTranslation()); 1345 } 1346 setQsTranslation(mQsExpansionHeight); 1347 } 1348 1349 private float getHeaderTranslation() { 1350 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1351 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1352 return 0; 1353 } 1354 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1355 if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) { 1356 return 0; 1357 } else { 1358 return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight; 1359 } 1360 } 1361 return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR; 1362 } 1363 1364 private void updateHeaderKeyguard() { 1365 float alphaNotifications; 1366 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1367 1368 // When on Keyguard, we hide the header as soon as the top card of the notification 1369 // stack scroller is close enough (collision distance) to the bottom of the header. 1370 alphaNotifications = getNotificationsTopY() 1371 / 1372 (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance); 1373 } else { 1374 1375 // In SHADE_LOCKED, the top card is already really close to the header. Hide it as 1376 // soon as we start translating the stack. 1377 alphaNotifications = getNotificationsTopY() / mKeyguardStatusBar.getHeight(); 1378 } 1379 alphaNotifications = MathUtils.constrain(alphaNotifications, 0, 1); 1380 alphaNotifications = (float) Math.pow(alphaNotifications, 0.75); 1381 float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2); 1382 mKeyguardStatusBar.setAlpha(Math.min(alphaNotifications, alphaQsExpansion)); 1383 mKeyguardBottomArea.setAlpha(Math.min(1 - getQsExpansionFraction(), alphaNotifications)); 1384 setQsTranslation(mQsExpansionHeight); 1385 } 1386 1387 private float getNotificationsTopY() { 1388 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1389 return getExpandedHeight(); 1390 } 1391 return mNotificationStackScroller.getNotificationsTopY(); 1392 } 1393 1394 @Override 1395 protected void onExpandingStarted() { 1396 super.onExpandingStarted(); 1397 mNotificationStackScroller.onExpansionStarted(); 1398 mIsExpanding = true; 1399 mQsExpandedWhenExpandingStarted = mQsExpanded; 1400 if (mQsExpanded) { 1401 onQsExpansionStarted(); 1402 } 1403 } 1404 1405 @Override 1406 protected void onExpandingFinished() { 1407 super.onExpandingFinished(); 1408 mNotificationStackScroller.onExpansionStopped(); 1409 mIsExpanding = false; 1410 mScrollYOverride = -1; 1411 if (mExpandedHeight == 0f) { 1412 setListening(false); 1413 } else { 1414 setListening(true); 1415 } 1416 mTwoFingerQsExpand = false; 1417 mTwoFingerQsExpandPossible = false; 1418 } 1419 1420 private void setListening(boolean listening) { 1421 mHeader.setListening(listening); 1422 mKeyguardStatusBar.setListening(listening); 1423 mQsPanel.setListening(listening); 1424 } 1425 1426 @Override 1427 public void instantExpand() { 1428 super.instantExpand(); 1429 setListening(true); 1430 } 1431 1432 @Override 1433 protected void setOverExpansion(float overExpansion, boolean isPixels) { 1434 if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) { 1435 return; 1436 } 1437 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 1438 mNotificationStackScroller.setOnHeightChangedListener(null); 1439 if (isPixels) { 1440 mNotificationStackScroller.setOverScrolledPixels( 1441 overExpansion, true /* onTop */, false /* animate */); 1442 } else { 1443 mNotificationStackScroller.setOverScrollAmount( 1444 overExpansion, true /* onTop */, false /* animate */); 1445 } 1446 mNotificationStackScroller.setOnHeightChangedListener(this); 1447 } 1448 } 1449 1450 @Override 1451 protected void onTrackingStarted() { 1452 super.onTrackingStarted(); 1453 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1454 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1455 mAfforanceHelper.animateHideLeftRightIcon(); 1456 } 1457 if (mQsExpanded) { 1458 mTwoFingerQsExpand = true; 1459 } 1460 } 1461 1462 @Override 1463 protected void onTrackingStopped(boolean expand) { 1464 super.onTrackingStopped(expand); 1465 if (expand) { 1466 mNotificationStackScroller.setOverScrolledPixels( 1467 0.0f, true /* onTop */, true /* animate */); 1468 } 1469 if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1470 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1471 if (!mHintAnimationRunning) { 1472 mAfforanceHelper.reset(true); 1473 } 1474 } 1475 if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1476 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1477 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1478 lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null); 1479 lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator); 1480 } 1481 } 1482 1483 @Override 1484 public void onHeightChanged(ExpandableView view) { 1485 1486 // Block update if we are in quick settings and just the top padding changed 1487 // (i.e. view == null). 1488 if (view == null && mQsExpanded) { 1489 return; 1490 } 1491 requestPanelHeightUpdate(); 1492 } 1493 1494 @Override 1495 public void onReset(ExpandableView view) { 1496 } 1497 1498 @Override 1499 public void onScrollChanged() { 1500 if (mQsExpanded) { 1501 requestScrollerTopPaddingUpdate(false /* animate */); 1502 requestPanelHeightUpdate(); 1503 } 1504 } 1505 1506 @Override 1507 protected void onConfigurationChanged(Configuration newConfig) { 1508 super.onConfigurationChanged(newConfig); 1509 mAfforanceHelper.onConfigurationChanged(); 1510 } 1511 1512 @Override 1513 public void onClick(View v) { 1514 if (v == mHeader) { 1515 onQsExpansionStarted(); 1516 if (mQsExpanded) { 1517 flingSettings(0 /* vel */, false /* expand */); 1518 } else if (mQsExpansionEnabled) { 1519 flingSettings(0 /* vel */, true /* expand */); 1520 } 1521 } 1522 } 1523 1524 @Override 1525 public void onAnimationToSideStarted(boolean rightPage) { 1526 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage; 1527 mIsLaunchTransitionRunning = true; 1528 mLaunchAnimationEndRunnable = null; 1529 if (start) { 1530 mKeyguardBottomArea.launchPhone(); 1531 } else { 1532 mSecureCameraLaunchManager.startSecureCameraLaunch(); 1533 } 1534 mBlockTouches = true; 1535 } 1536 1537 @Override 1538 public void onAnimationToSideEnded() { 1539 mIsLaunchTransitionRunning = false; 1540 mIsLaunchTransitionFinished = true; 1541 if (mLaunchAnimationEndRunnable != null) { 1542 mLaunchAnimationEndRunnable.run(); 1543 mLaunchAnimationEndRunnable = null; 1544 } 1545 } 1546 1547 @Override 1548 protected void onEdgeClicked(boolean right) { 1549 if ((right && getRightIcon().getVisibility() != View.VISIBLE) 1550 || (!right && getLeftIcon().getVisibility() != View.VISIBLE)) { 1551 return; 1552 } 1553 mHintAnimationRunning = true; 1554 mAfforanceHelper.startHintAnimation(right, new Runnable() { 1555 @Override 1556 public void run() { 1557 mHintAnimationRunning = false; 1558 mStatusBar.onHintFinished(); 1559 } 1560 }); 1561 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right; 1562 if (start) { 1563 mStatusBar.onPhoneHintStarted(); 1564 } else { 1565 mStatusBar.onCameraHintStarted(); 1566 } 1567 } 1568 1569 @Override 1570 protected void startUnlockHintAnimation() { 1571 super.startUnlockHintAnimation(); 1572 startHighlightIconAnimation(getCenterIcon()); 1573 } 1574 1575 /** 1576 * Starts the highlight (making it fully opaque) animation on an icon. 1577 */ 1578 private void startHighlightIconAnimation(final KeyguardAffordanceView icon) { 1579 icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1580 mFastOutSlowInInterpolator, new Runnable() { 1581 @Override 1582 public void run() { 1583 icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, 1584 true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1585 mFastOutSlowInInterpolator, null); 1586 } 1587 }); 1588 } 1589 1590 @Override 1591 public float getPageWidth() { 1592 return getWidth(); 1593 } 1594 1595 @Override 1596 public void onSwipingStarted() { 1597 mSecureCameraLaunchManager.onSwipingStarted(); 1598 requestDisallowInterceptTouchEvent(true); 1599 mOnlyAffordanceInThisMotion = true; 1600 } 1601 1602 @Override 1603 public KeyguardAffordanceView getLeftIcon() { 1604 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1605 ? mKeyguardBottomArea.getCameraView() 1606 : mKeyguardBottomArea.getPhoneView(); 1607 } 1608 1609 @Override 1610 public KeyguardAffordanceView getCenterIcon() { 1611 return mKeyguardBottomArea.getLockIcon(); 1612 } 1613 1614 @Override 1615 public KeyguardAffordanceView getRightIcon() { 1616 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1617 ? mKeyguardBottomArea.getPhoneView() 1618 : mKeyguardBottomArea.getCameraView(); 1619 } 1620 1621 @Override 1622 public View getLeftPreview() { 1623 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1624 ? mKeyguardBottomArea.getCameraPreview() 1625 : mKeyguardBottomArea.getPhonePreview(); 1626 } 1627 1628 @Override 1629 public View getRightPreview() { 1630 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1631 ? mKeyguardBottomArea.getPhonePreview() 1632 : mKeyguardBottomArea.getCameraPreview(); 1633 } 1634 1635 @Override 1636 protected float getPeekHeight() { 1637 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 1638 return mNotificationStackScroller.getPeekHeight(); 1639 } else { 1640 return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR; 1641 } 1642 } 1643 1644 @Override 1645 protected float getCannedFlingDurationFactor() { 1646 if (mQsExpanded) { 1647 return 0.7f; 1648 } else { 1649 return 0.6f; 1650 } 1651 } 1652 1653 @Override 1654 protected boolean fullyExpandedClearAllVisible() { 1655 return mNotificationStackScroller.isDismissViewNotGone() 1656 && mNotificationStackScroller.isScrolledToBottom() && !mTwoFingerQsExpand; 1657 } 1658 1659 @Override 1660 protected boolean isClearAllVisible() { 1661 return mNotificationStackScroller.isDismissViewVisible(); 1662 } 1663 1664 @Override 1665 protected int getClearAllHeight() { 1666 return mNotificationStackScroller.getDismissViewHeight(); 1667 } 1668 1669 @Override 1670 protected boolean isTrackingBlocked() { 1671 return mConflictingQsExpansionGesture && mQsExpanded; 1672 } 1673 1674 public void notifyVisibleChildrenChanged() { 1675 if (mNotificationStackScroller.getNotGoneChildCount() != 0) { 1676 mReserveNotificationSpace.setVisibility(View.VISIBLE); 1677 } else { 1678 mReserveNotificationSpace.setVisibility(View.GONE); 1679 } 1680 } 1681 1682 public boolean isQsExpanded() { 1683 return mQsExpanded; 1684 } 1685 1686 public boolean isQsDetailShowing() { 1687 return mQsPanel.isShowingDetail(); 1688 } 1689 1690 public void closeQsDetail() { 1691 mQsPanel.closeDetail(); 1692 } 1693 1694 @Override 1695 public boolean shouldDelayChildPressedState() { 1696 return true; 1697 } 1698 1699 public boolean isLaunchTransitionFinished() { 1700 return mIsLaunchTransitionFinished; 1701 } 1702 1703 public boolean isLaunchTransitionRunning() { 1704 return mIsLaunchTransitionRunning; 1705 } 1706 1707 public void setLaunchTransitionEndRunnable(Runnable r) { 1708 mLaunchAnimationEndRunnable = r; 1709 } 1710 1711 public void setEmptyDragAmount(float amount) { 1712 float factor = 0.8f; 1713 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 1714 factor = 0.4f; 1715 } else if (!mStatusBar.hasActiveNotifications()) { 1716 factor = 0.4f; 1717 } 1718 mEmptyDragAmount = amount * factor; 1719 positionClockAndNotifications(); 1720 } 1721 1722 private static float interpolate(float t, float start, float end) { 1723 return (1 - t) * start + t * end; 1724 } 1725 1726 private void updateKeyguardStatusBarVisibility() { 1727 mKeyguardStatusBar.setVisibility(mKeyguardShowing && !mDozing ? VISIBLE : INVISIBLE); 1728 } 1729 1730 public void setDozing(boolean dozing) { 1731 if (dozing == mDozing) return; 1732 mDozing = dozing; 1733 if (mDozing) { 1734 setBackgroundColorAlpha(this, DOZE_BACKGROUND_COLOR, 0xff, false /*animate*/); 1735 } else { 1736 setBackgroundColorAlpha(this, DOZE_BACKGROUND_COLOR, 0, true /*animate*/); 1737 } 1738 updateKeyguardStatusBarVisibility(); 1739 } 1740 1741 public boolean isDozing() { 1742 return mDozing; 1743 } 1744 1745 private static void setBackgroundColorAlpha(final View target, int rgb, int targetAlpha, 1746 boolean animate) { 1747 int currentAlpha = getBackgroundAlpha(target); 1748 if (currentAlpha == targetAlpha) { 1749 return; 1750 } 1751 final int r = Color.red(rgb); 1752 final int g = Color.green(rgb); 1753 final int b = Color.blue(rgb); 1754 Object runningAnim = target.getTag(TAG_KEY_ANIM); 1755 if (runningAnim instanceof ValueAnimator) { 1756 ((ValueAnimator) runningAnim).cancel(); 1757 } 1758 if (!animate) { 1759 target.setBackgroundColor(Color.argb(targetAlpha, r, g, b)); 1760 return; 1761 } 1762 ValueAnimator anim = ValueAnimator.ofInt(currentAlpha, targetAlpha); 1763 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 1764 @Override 1765 public void onAnimationUpdate(ValueAnimator animation) { 1766 int value = (int) animation.getAnimatedValue(); 1767 target.setBackgroundColor(Color.argb(value, r, g, b)); 1768 } 1769 }); 1770 anim.setDuration(DOZE_BACKGROUND_ANIM_DURATION); 1771 anim.addListener(new AnimatorListenerAdapter() { 1772 @Override 1773 public void onAnimationEnd(Animator animation) { 1774 target.setTag(TAG_KEY_ANIM, null); 1775 } 1776 }); 1777 anim.start(); 1778 target.setTag(TAG_KEY_ANIM, anim); 1779 } 1780 1781 private static int getBackgroundAlpha(View view) { 1782 if (view.getBackground() instanceof ColorDrawable) { 1783 ColorDrawable drawable = (ColorDrawable) view.getBackground(); 1784 return Color.alpha(drawable.getColor()); 1785 } else { 1786 return 0; 1787 } 1788 } 1789 1790 public void setShadeEmpty(boolean shadeEmpty) { 1791 mShadeEmpty = shadeEmpty; 1792 updateEmptyShadeView(); 1793 } 1794 1795 private void updateEmptyShadeView() { 1796 1797 // Hide "No notifications" in QS. 1798 mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded); 1799 } 1800 1801 public void setQsScrimEnabled(boolean qsScrimEnabled) { 1802 boolean changed = mQsScrimEnabled != qsScrimEnabled; 1803 mQsScrimEnabled = qsScrimEnabled; 1804 if (changed) { 1805 updateQsState(); 1806 } 1807 } 1808 1809 public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) { 1810 mKeyguardUserSwitcher = keyguardUserSwitcher; 1811 } 1812 1813 private final Runnable mUpdateHeader = new Runnable() { 1814 @Override 1815 public void run() { 1816 mHeader.updateEverything(); 1817 } 1818 }; 1819 1820 public void onScreenTurnedOn() { 1821 mKeyguardStatusView.refreshTime(); 1822 } 1823} 1824