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