NotificationPanelView.java revision 90e0df291f0be8f84cad3b1a8834a60ddf7d352e
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.ValueAnimator; 23import android.content.Context; 24import android.content.res.Configuration; 25import android.util.AttributeSet; 26import android.view.MotionEvent; 27import android.view.VelocityTracker; 28import android.view.View; 29import android.view.ViewGroup; 30import android.view.ViewTreeObserver; 31import android.view.accessibility.AccessibilityEvent; 32import android.view.animation.AnimationUtils; 33import android.view.animation.Interpolator; 34import android.widget.LinearLayout; 35import android.widget.TextView; 36 37import com.android.systemui.R; 38import com.android.systemui.qs.QSPanel; 39import com.android.systemui.statusbar.ExpandableView; 40import com.android.systemui.statusbar.FlingAnimationUtils; 41import com.android.systemui.statusbar.GestureRecorder; 42import com.android.systemui.statusbar.KeyguardAffordanceView; 43import com.android.systemui.statusbar.MirrorView; 44import com.android.systemui.statusbar.StatusBarState; 45import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 46import com.android.systemui.statusbar.stack.StackStateAnimator; 47 48public class NotificationPanelView extends PanelView implements 49 ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, 50 View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener, 51 KeyguardAffordanceHelper.Callback { 52 53 // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is 54 // changed. 55 private static final int CAP_HEIGHT = 1456; 56 private static final int FONT_HEIGHT = 2163; 57 58 private static final float HEADER_RUBBERBAND_FACTOR = 2.15f; 59 private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f; 60 61 private KeyguardAffordanceHelper mAfforanceHelper; 62 private StatusBarHeaderView mHeader; 63 private View mQsContainer; 64 private QSPanel mQsPanel; 65 private View mKeyguardStatusView; 66 private ObservableScrollView mScrollView; 67 private TextView mClockView; 68 private View mReserveNotificationSpace; 69 private MirrorView mSystemIconsCopy; 70 71 private NotificationStackScrollLayout mNotificationStackScroller; 72 private int mNotificationTopPadding; 73 private boolean mAnimateNextTopPaddingChange; 74 75 private int mTrackingPointer; 76 private VelocityTracker mVelocityTracker; 77 private boolean mQsTracking; 78 79 /** 80 * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and 81 * the expansion for quick settings. 82 */ 83 private boolean mConflictingQsExpansionGesture; 84 85 /** 86 * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't 87 * intercepted yet. 88 */ 89 private boolean mIntercepting; 90 private boolean mQsExpanded; 91 private boolean mQsExpandedWhenExpandingStarted; 92 private boolean mQsFullyExpanded; 93 private boolean mKeyguardShowing; 94 private int mStatusBarState; 95 private float mInitialHeightOnTouch; 96 private float mInitialTouchX; 97 private float mInitialTouchY; 98 private float mLastTouchX; 99 private float mLastTouchY; 100 private float mQsExpansionHeight; 101 private int mQsMinExpansionHeight; 102 private int mQsMaxExpansionHeight; 103 private int mQsPeekHeight; 104 private boolean mStackScrollerOverscrolling; 105 private boolean mQsExpansionFromOverscroll; 106 private boolean mQsExpansionEnabled = true; 107 private ValueAnimator mQsExpansionAnimator; 108 private FlingAnimationUtils mFlingAnimationUtils; 109 private int mStatusBarMinHeight; 110 private boolean mUnlockIconActive; 111 private int mNotificationsHeaderCollideDistance; 112 private int mUnlockMoveDistance; 113 private float mEmptyDragAmount; 114 115 private Interpolator mFastOutSlowInInterpolator; 116 private Interpolator mFastOutLinearInterpolator; 117 private ObjectAnimator mClockAnimator; 118 private int mClockAnimationTarget = -1; 119 private int mTopPaddingAdjustment; 120 private KeyguardClockPositionAlgorithm mClockPositionAlgorithm = 121 new KeyguardClockPositionAlgorithm(); 122 private KeyguardClockPositionAlgorithm.Result mClockPositionResult = 123 new KeyguardClockPositionAlgorithm.Result(); 124 private boolean mIsExpanding; 125 126 private boolean mBlockTouches; 127 private int mNotificationScrimWaitDistance; 128 private boolean mTwoFingerQsExpand; 129 private boolean mTwoFingerQsExpandPossible; 130 131 /** 132 * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still 133 * need to take this into account in our panel height calculation. 134 */ 135 private int mScrollYOverride = -1; 136 private boolean mQsAnimatorExpand; 137 private boolean mIsLaunchTransitionFinished; 138 private boolean mIsLaunchTransitionRunning; 139 private Runnable mLaunchAnimationEndRunnable; 140 private boolean mOnlyAffordanceInThisMotion; 141 142 public NotificationPanelView(Context context, AttributeSet attrs) { 143 super(context, attrs); 144 mSystemIconsCopy = new MirrorView(context); 145 } 146 147 public void setStatusBar(PhoneStatusBar bar) { 148 mStatusBar = bar; 149 } 150 151 @Override 152 protected void onFinishInflate() { 153 super.onFinishInflate(); 154 mHeader = (StatusBarHeaderView) findViewById(R.id.header); 155 mHeader.setOnClickListener(this); 156 mKeyguardStatusView = findViewById(R.id.keyguard_status_view); 157 mQsContainer = findViewById(R.id.quick_settings_container); 158 mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel); 159 mClockView = (TextView) findViewById(R.id.clock_view); 160 mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view); 161 mScrollView.setListener(this); 162 mReserveNotificationSpace = findViewById(R.id.reserve_notification_space); 163 mNotificationStackScroller = (NotificationStackScrollLayout) 164 findViewById(R.id.notification_stack_scroller); 165 mNotificationStackScroller.setOnHeightChangedListener(this); 166 mNotificationStackScroller.setOverscrollTopChangedListener(this); 167 mNotificationStackScroller.setScrollView(mScrollView); 168 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), 169 android.R.interpolator.fast_out_slow_in); 170 mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(), 171 android.R.interpolator.fast_out_linear_in); 172 mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); 173 mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext()); 174 } 175 176 @Override 177 protected void loadDimens() { 178 super.loadDimens(); 179 mNotificationTopPadding = getResources().getDimensionPixelSize( 180 R.dimen.notifications_top_padding); 181 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f); 182 mStatusBarMinHeight = getResources().getDimensionPixelSize( 183 com.android.internal.R.dimen.status_bar_height); 184 mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height); 185 mNotificationsHeaderCollideDistance = 186 getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance); 187 mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance); 188 mClockPositionAlgorithm.loadDimens(getResources()); 189 mNotificationScrimWaitDistance = 190 getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance); 191 } 192 193 @Override 194 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 195 super.onLayout(changed, left, top, right, bottom); 196 197 // Update Clock Pivot 198 mKeyguardStatusView.setPivotX(getWidth() / 2); 199 mKeyguardStatusView.setPivotY( 200 (FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize()); 201 202 // Calculate quick setting heights. 203 mQsMinExpansionHeight = mHeader.getCollapsedHeight() + mQsPeekHeight; 204 mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight(); 205 if (mQsExpanded) { 206 if (mQsFullyExpanded) { 207 mQsExpansionHeight = mQsMaxExpansionHeight; 208 requestScrollerTopPaddingUpdate(false /* animate */); 209 } 210 } else { 211 if (!mStackScrollerOverscrolling) { 212 setQsExpansion(mQsMinExpansionHeight); 213 } 214 positionClockAndNotifications(); 215 mNotificationStackScroller.setStackHeight(getExpandedHeight()); 216 } 217 } 218 219 /** 220 * Positions the clock and notifications dynamically depending on how many notifications are 221 * showing. 222 */ 223 private void positionClockAndNotifications() { 224 boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending(); 225 int stackScrollerPadding; 226 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 227 int bottom = mHeader.getCollapsedHeight(); 228 stackScrollerPadding = bottom + mQsPeekHeight 229 + mNotificationTopPadding; 230 mTopPaddingAdjustment = 0; 231 } else { 232 mClockPositionAlgorithm.setup( 233 mStatusBar.getMaxKeyguardNotifications(), 234 getMaxPanelHeight(), 235 getExpandedHeight(), 236 mNotificationStackScroller.getNotGoneChildCount(), 237 getHeight(), 238 mKeyguardStatusView.getHeight(), 239 mEmptyDragAmount); 240 mClockPositionAlgorithm.run(mClockPositionResult); 241 if (animate || mClockAnimator != null) { 242 startClockAnimation(mClockPositionResult.clockY); 243 } else { 244 mKeyguardStatusView.setY(mClockPositionResult.clockY); 245 } 246 updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale); 247 stackScrollerPadding = mClockPositionResult.stackScrollerPadding; 248 mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment; 249 } 250 mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding); 251 requestScrollerTopPaddingUpdate(animate); 252 } 253 254 private void startClockAnimation(int y) { 255 if (mClockAnimationTarget == y) { 256 return; 257 } 258 mClockAnimationTarget = y; 259 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 260 @Override 261 public boolean onPreDraw() { 262 getViewTreeObserver().removeOnPreDrawListener(this); 263 if (mClockAnimator != null) { 264 mClockAnimator.removeAllListeners(); 265 mClockAnimator.cancel(); 266 } 267 mClockAnimator = ObjectAnimator 268 .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget); 269 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator); 270 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 271 mClockAnimator.addListener(new AnimatorListenerAdapter() { 272 @Override 273 public void onAnimationEnd(Animator animation) { 274 mClockAnimator = null; 275 mClockAnimationTarget = -1; 276 } 277 }); 278 mClockAnimator.start(); 279 return true; 280 } 281 }); 282 } 283 284 private void updateClock(float alpha, float scale) { 285 mKeyguardStatusView.setAlpha(alpha); 286 mKeyguardStatusView.setScaleX(scale); 287 mKeyguardStatusView.setScaleY(scale); 288 } 289 290 public void animateToFullShade() { 291 mAnimateNextTopPaddingChange = true; 292 mNotificationStackScroller.goToFullShade(); 293 requestLayout(); 294 } 295 296 public void setQsExpansionEnabled(boolean qsExpansionEnabled) { 297 mQsExpansionEnabled = qsExpansionEnabled; 298 } 299 300 @Override 301 public void resetViews() { 302 mIsLaunchTransitionFinished = false; 303 mBlockTouches = false; 304 mUnlockIconActive = false; 305 mAfforanceHelper.reset(true); 306 closeQs(); 307 mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */, 308 true /* cancelAnimators */); 309 } 310 311 public void closeQs() { 312 cancelAnimation(); 313 setQsExpansion(mQsMinExpansionHeight); 314 } 315 316 public void animateCloseQs() { 317 if (mQsExpansionAnimator != null) { 318 if (!mQsAnimatorExpand) { 319 return; 320 } 321 float height = mQsExpansionHeight; 322 mQsExpansionAnimator.cancel(); 323 setQsExpansion(height); 324 } 325 flingSettings(0 /* vel */, false); 326 } 327 328 public void openQs() { 329 cancelAnimation(); 330 if (mQsExpansionEnabled) { 331 setQsExpansion(mQsMaxExpansionHeight); 332 } 333 } 334 335 @Override 336 public void fling(float vel, boolean always) { 337 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); 338 if (gr != null) { 339 gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); 340 } 341 super.fling(vel, always); 342 } 343 344 @Override 345 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 346 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 347 event.getText() 348 .add(getContext().getString(R.string.accessibility_desc_notification_shade)); 349 return true; 350 } 351 352 return super.dispatchPopulateAccessibilityEvent(event); 353 } 354 355 @Override 356 public boolean onInterceptTouchEvent(MotionEvent event) { 357 if (mBlockTouches) { 358 return false; 359 } 360 resetDownStates(event); 361 int pointerIndex = event.findPointerIndex(mTrackingPointer); 362 if (pointerIndex < 0) { 363 pointerIndex = 0; 364 mTrackingPointer = event.getPointerId(pointerIndex); 365 } 366 final float x = event.getX(pointerIndex); 367 final float y = event.getY(pointerIndex); 368 369 switch (event.getActionMasked()) { 370 case MotionEvent.ACTION_DOWN: 371 mIntercepting = true; 372 mInitialTouchY = y; 373 mInitialTouchX = x; 374 initVelocityTracker(); 375 trackMovement(event); 376 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { 377 getParent().requestDisallowInterceptTouchEvent(true); 378 } 379 if (mQsExpansionAnimator != null) { 380 onQsExpansionStarted(); 381 mInitialHeightOnTouch = mQsExpansionHeight; 382 mQsTracking = true; 383 mIntercepting = false; 384 mNotificationStackScroller.removeLongPressCallback(); 385 } 386 break; 387 case MotionEvent.ACTION_POINTER_UP: 388 final int upPointer = event.getPointerId(event.getActionIndex()); 389 if (mTrackingPointer == upPointer) { 390 // gesture is ongoing, find a new pointer to track 391 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 392 mTrackingPointer = event.getPointerId(newIndex); 393 mInitialTouchX = event.getX(newIndex); 394 mInitialTouchY = event.getY(newIndex); 395 } 396 break; 397 398 case MotionEvent.ACTION_MOVE: 399 final float h = y - mInitialTouchY; 400 trackMovement(event); 401 if (mQsTracking) { 402 403 // Already tracking because onOverscrolled was called. We need to update here 404 // so we don't stop for a frame until the next touch event gets handled in 405 // onTouchEvent. 406 setQsExpansion(h + mInitialHeightOnTouch); 407 trackMovement(event); 408 mIntercepting = false; 409 return true; 410 } 411 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) 412 && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { 413 onQsExpansionStarted(); 414 mInitialHeightOnTouch = mQsExpansionHeight; 415 mInitialTouchY = y; 416 mInitialTouchX = x; 417 mQsTracking = true; 418 mIntercepting = false; 419 mNotificationStackScroller.removeLongPressCallback(); 420 return true; 421 } 422 break; 423 424 case MotionEvent.ACTION_CANCEL: 425 case MotionEvent.ACTION_UP: 426 trackMovement(event); 427 if (mQsTracking) { 428 flingQsWithCurrentVelocity(); 429 mQsTracking = false; 430 } 431 mIntercepting = false; 432 break; 433 } 434 return !mQsExpanded && super.onInterceptTouchEvent(event); 435 } 436 437 private void resetDownStates(MotionEvent event) { 438 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 439 mOnlyAffordanceInThisMotion = false; 440 } 441 } 442 443 @Override 444 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 445 446 // Block request when interacting with the scroll view so we can still intercept the 447 // scrolling when QS is expanded. 448 if (mScrollView.isHandlingTouchEvent()) { 449 return; 450 } 451 super.requestDisallowInterceptTouchEvent(disallowIntercept); 452 } 453 454 private void flingQsWithCurrentVelocity() { 455 float vel = getCurrentVelocity(); 456 flingSettings(vel, flingExpandsQs(vel)); 457 } 458 459 private boolean flingExpandsQs(float vel) { 460 if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { 461 return getQsExpansionFraction() > 0.5f; 462 } else { 463 return vel > 0; 464 } 465 } 466 467 private float getQsExpansionFraction() { 468 return (mQsExpansionHeight - mQsMinExpansionHeight) 469 / (getTempQsMaxExpansion() - mQsMinExpansionHeight); 470 } 471 472 @Override 473 public boolean onTouchEvent(MotionEvent event) { 474 if (mBlockTouches) { 475 return false; 476 } 477 resetDownStates(event); 478 if ((!mIsExpanding || mHintAnimationRunning) 479 && !mQsExpanded 480 && mStatusBar.getBarState() != StatusBarState.SHADE) { 481 mAfforanceHelper.onTouchEvent(event); 482 } 483 if (mOnlyAffordanceInThisMotion) { 484 return true; 485 } 486 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f 487 && mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 488 489 // Down in the empty area while fully expanded - go to QS. 490 mQsTracking = true; 491 mConflictingQsExpansionGesture = true; 492 onQsExpansionStarted(); 493 mInitialHeightOnTouch = mQsExpansionHeight; 494 mInitialTouchY = event.getX(); 495 mInitialTouchX = event.getY(); 496 } 497 if (mExpandedHeight != 0) { 498 handleQsDown(event); 499 } 500 if (!mTwoFingerQsExpand && (mQsTracking || mQsExpanded)) { 501 onQsTouch(event); 502 if (!mConflictingQsExpansionGesture) { 503 return true; 504 } 505 } 506 if (event.getActionMasked() == MotionEvent.ACTION_CANCEL 507 || event.getActionMasked() == MotionEvent.ACTION_UP) { 508 mConflictingQsExpansionGesture = false; 509 } 510 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0) { 511 mTwoFingerQsExpandPossible = true; 512 } 513 if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN 514 && event.getPointerCount() == 2 515 && event.getY(event.getActionIndex()) < mStatusBarMinHeight) { 516 mTwoFingerQsExpand = true; 517 requestPanelHeightUpdate(); 518 } 519 super.onTouchEvent(event); 520 return true; 521 } 522 523 private void handleQsDown(MotionEvent event) { 524 if (event.getActionMasked() == MotionEvent.ACTION_DOWN 525 && shouldQuickSettingsIntercept(event.getX(), event.getY(), 0)) { 526 mQsTracking = true; 527 onQsExpansionStarted(); 528 mInitialHeightOnTouch = mQsExpansionHeight; 529 mInitialTouchY = event.getX(); 530 mInitialTouchX = event.getY(); 531 } 532 } 533 534 @Override 535 protected boolean flingExpands(float vel, float vectorVel) { 536 boolean expands = super.flingExpands(vel, vectorVel); 537 538 // If we are already running a QS expansion, make sure that we keep the panel open. 539 if (mQsExpansionAnimator != null) { 540 expands = true; 541 } 542 return expands; 543 } 544 545 @Override 546 protected boolean hasConflictingGestures() { 547 return mStatusBar.getBarState() != StatusBarState.SHADE; 548 } 549 550 private void onQsTouch(MotionEvent event) { 551 int pointerIndex = event.findPointerIndex(mTrackingPointer); 552 if (pointerIndex < 0) { 553 pointerIndex = 0; 554 mTrackingPointer = event.getPointerId(pointerIndex); 555 } 556 final float y = event.getY(pointerIndex); 557 final float x = event.getX(pointerIndex); 558 559 switch (event.getActionMasked()) { 560 case MotionEvent.ACTION_DOWN: 561 mQsTracking = true; 562 mInitialTouchY = y; 563 mInitialTouchX = x; 564 onQsExpansionStarted(); 565 mInitialHeightOnTouch = mQsExpansionHeight; 566 initVelocityTracker(); 567 trackMovement(event); 568 break; 569 570 case MotionEvent.ACTION_POINTER_UP: 571 final int upPointer = event.getPointerId(event.getActionIndex()); 572 if (mTrackingPointer == upPointer) { 573 // gesture is ongoing, find a new pointer to track 574 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 575 final float newY = event.getY(newIndex); 576 final float newX = event.getX(newIndex); 577 mTrackingPointer = event.getPointerId(newIndex); 578 mInitialHeightOnTouch = mQsExpansionHeight; 579 mInitialTouchY = newY; 580 mInitialTouchX = newX; 581 } 582 break; 583 584 case MotionEvent.ACTION_MOVE: 585 final float h = y - mInitialTouchY; 586 setQsExpansion(h + mInitialHeightOnTouch); 587 trackMovement(event); 588 break; 589 590 case MotionEvent.ACTION_UP: 591 case MotionEvent.ACTION_CANCEL: 592 mQsTracking = false; 593 mTrackingPointer = -1; 594 trackMovement(event); 595 float fraction = getQsExpansionFraction(); 596 if ((fraction != 0f || y >= mInitialTouchY) 597 && (fraction != 1f || y <= mInitialTouchY)) { 598 flingQsWithCurrentVelocity(); 599 } else { 600 mScrollYOverride = -1; 601 } 602 if (mVelocityTracker != null) { 603 mVelocityTracker.recycle(); 604 mVelocityTracker = null; 605 } 606 break; 607 } 608 } 609 610 @Override 611 public void onOverscrolled(int amount) { 612 if (mIntercepting) { 613 onQsExpansionStarted(amount); 614 mInitialHeightOnTouch = mQsExpansionHeight; 615 mInitialTouchY = mLastTouchY; 616 mInitialTouchX = mLastTouchX; 617 mQsTracking = true; 618 } 619 } 620 621 @Override 622 public void onOverscrollTopChanged(float amount, boolean isRubberbanded) { 623 cancelAnimation(); 624 float rounded = amount >= 1f ? amount : 0f; 625 mStackScrollerOverscrolling = rounded != 0f && isRubberbanded; 626 mQsExpansionFromOverscroll = rounded != 0f; 627 updateQsState(); 628 setQsExpansion(mQsMinExpansionHeight + rounded); 629 } 630 631 @Override 632 public void flingTopOverscroll(float velocity, boolean open) { 633 setQsExpansion(mQsExpansionHeight); 634 flingSettings(velocity, open, new Runnable() { 635 @Override 636 public void run() { 637 mStackScrollerOverscrolling = false; 638 mQsExpansionFromOverscroll = false; 639 updateQsState(); 640 } 641 }); 642 } 643 644 private void onQsExpansionStarted() { 645 onQsExpansionStarted(0); 646 } 647 648 private void onQsExpansionStarted(int overscrollAmount) { 649 cancelAnimation(); 650 651 // Reset scroll position and apply that position to the expanded height. 652 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount; 653 if (mScrollView.getScrollY() != 0) { 654 mScrollYOverride = mScrollView.getScrollY(); 655 } 656 mScrollView.scrollTo(0, 0); 657 setQsExpansion(height); 658 } 659 660 private void setQsExpanded(boolean expanded) { 661 boolean changed = mQsExpanded != expanded; 662 if (changed) { 663 mQsExpanded = expanded; 664 updateQsState(); 665 requestPanelHeightUpdate(); 666 mNotificationStackScroller.setInterceptDelegateEnabled(expanded); 667 mStatusBar.setQsExpanded(expanded); 668 } 669 } 670 671 public void setBarState(int statusBarState) { 672 boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD 673 || statusBarState == StatusBarState.SHADE_LOCKED; 674 if (!mKeyguardShowing && keyguardShowing) { 675 setQsTranslation(mQsExpansionHeight); 676 mHeader.setTranslationY(0f); 677 } 678 mStatusBarState = statusBarState; 679 mKeyguardShowing = keyguardShowing; 680 updateQsState(); 681 } 682 683 private void updateQsState() { 684 boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling; 685 mHeader.setExpanded(expandVisually, mStackScrollerOverscrolling); 686 mNotificationStackScroller.setScrollingEnabled(mStatusBarState != StatusBarState.KEYGUARD 687 && (!mQsExpanded || mQsExpansionFromOverscroll)); 688 mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE); 689 mQsContainer.setVisibility( 690 mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE); 691 mScrollView.setTouchEnabled(mQsExpanded); 692 } 693 694 private void setQsExpansion(float height) { 695 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); 696 mQsFullyExpanded = height == mQsMaxExpansionHeight; 697 if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) { 698 setQsExpanded(true); 699 } else if (height <= mQsMinExpansionHeight && mQsExpanded) { 700 setQsExpanded(false); 701 } 702 mQsExpansionHeight = height; 703 mHeader.setExpansion(getQsExpansionFraction()); 704 setQsTranslation(height); 705 requestScrollerTopPaddingUpdate(false /* animate */); 706 updateNotificationScrim(height); 707 } 708 709 private void updateNotificationScrim(float height) { 710 int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance; 711 float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance); 712 progress = Math.max(0.0f, Math.min(progress, 1.0f)); 713 mNotificationStackScroller.setScrimAlpha(progress); 714 } 715 716 private void setQsTranslation(float height) { 717 mQsContainer.setY(height - mQsContainer.getHeight() + getHeaderTranslation()); 718 } 719 720 private void requestScrollerTopPaddingUpdate(boolean animate) { 721 mNotificationStackScroller.updateTopPadding(mQsExpansionHeight, 722 mScrollView.getScrollY(), 723 mAnimateNextTopPaddingChange || animate); 724 mAnimateNextTopPaddingChange = false; 725 } 726 727 private void trackMovement(MotionEvent event) { 728 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 729 mLastTouchX = event.getX(); 730 mLastTouchY = event.getY(); 731 } 732 733 private void initVelocityTracker() { 734 if (mVelocityTracker != null) { 735 mVelocityTracker.recycle(); 736 } 737 mVelocityTracker = VelocityTracker.obtain(); 738 } 739 740 private float getCurrentVelocity() { 741 if (mVelocityTracker == null) { 742 return 0; 743 } 744 mVelocityTracker.computeCurrentVelocity(1000); 745 return mVelocityTracker.getYVelocity(); 746 } 747 748 private void cancelAnimation() { 749 if (mQsExpansionAnimator != null) { 750 mQsExpansionAnimator.cancel(); 751 } 752 } 753 754 private void flingSettings(float vel, boolean expand) { 755 flingSettings(vel, expand, null); 756 } 757 758 private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable) { 759 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight; 760 if (target == mQsExpansionHeight) { 761 mScrollYOverride = -1; 762 if (onFinishRunnable != null) { 763 onFinishRunnable.run(); 764 } 765 return; 766 } 767 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target); 768 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel); 769 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 770 @Override 771 public void onAnimationUpdate(ValueAnimator animation) { 772 setQsExpansion((Float) animation.getAnimatedValue()); 773 } 774 }); 775 animator.addListener(new AnimatorListenerAdapter() { 776 @Override 777 public void onAnimationEnd(Animator animation) { 778 mScrollYOverride = -1; 779 mQsExpansionAnimator = null; 780 if (onFinishRunnable != null) { 781 onFinishRunnable.run(); 782 } 783 } 784 }); 785 animator.start(); 786 mQsExpansionAnimator = animator; 787 mQsAnimatorExpand = expand; 788 } 789 790 /** 791 * @return Whether we should intercept a gesture to open Quick Settings. 792 */ 793 private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { 794 if (!mQsExpansionEnabled) { 795 return false; 796 } 797 boolean onHeader = x >= mHeader.getLeft() && x <= mHeader.getRight() 798 && y >= mHeader.getTop() && y <= mHeader.getBottom(); 799 if (mQsExpanded) { 800 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0); 801 } else { 802 return onHeader; 803 } 804 } 805 806 @Override 807 public void setVisibility(int visibility) { 808 int oldVisibility = getVisibility(); 809 super.setVisibility(visibility); 810 if (visibility != oldVisibility) { 811 reparentStatusIcons(visibility == VISIBLE); 812 } 813 } 814 815 /** 816 * When the notification panel gets expanded, we need to move the status icons in the header 817 * card. 818 */ 819 private void reparentStatusIcons(boolean toHeader) { 820 if (mStatusBar == null) { 821 return; 822 } 823 LinearLayout systemIcons = mStatusBar.getSystemIcons(); 824 ViewGroup parent = ((ViewGroup) systemIcons.getParent()); 825 if (toHeader) { 826 int index = parent.indexOfChild(systemIcons); 827 parent.removeView(systemIcons); 828 mSystemIconsCopy.setMirroredView( 829 systemIcons, systemIcons.getWidth(), systemIcons.getHeight()); 830 parent.addView(mSystemIconsCopy, index); 831 mHeader.attachSystemIcons(systemIcons); 832 } else { 833 ViewGroup newParent = mStatusBar.getSystemIconArea(); 834 int index = newParent.indexOfChild(mSystemIconsCopy); 835 parent.removeView(systemIcons); 836 mHeader.onSystemIconsDetached(); 837 mSystemIconsCopy.setMirroredView(null, 0, 0); 838 newParent.removeView(mSystemIconsCopy); 839 newParent.addView(systemIcons, index); 840 } 841 } 842 843 @Override 844 protected boolean isScrolledToBottom() { 845 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 846 return true; 847 } 848 if (!isInSettings()) { 849 return mNotificationStackScroller.isScrolledToBottom(); 850 } 851 return super.isScrolledToBottom(); 852 } 853 854 @Override 855 protected int getMaxPanelHeight() { 856 int min = mStatusBarMinHeight; 857 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD 858 && mNotificationStackScroller.getNotGoneChildCount() == 0) { 859 int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount()) 860 * HEADER_RUBBERBAND_FACTOR); 861 min = Math.max(min, minHeight); 862 } 863 int maxHeight; 864 if (mTwoFingerQsExpand || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) { 865 maxHeight = (int) calculatePanelHeightQsExpanded(); 866 } else { 867 int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin(); 868 maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin 869 - mTopPaddingAdjustment; 870 maxHeight += mNotificationStackScroller.getTopPaddingOverflow(); 871 } 872 maxHeight = Math.max(maxHeight, min); 873 return maxHeight; 874 } 875 876 private boolean isInSettings() { 877 return mQsExpanded; 878 } 879 880 @Override 881 protected void onHeightUpdated(float expandedHeight) { 882 if (!mQsExpanded) { 883 positionClockAndNotifications(); 884 } 885 if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null 886 && !mQsExpansionFromOverscroll) { 887 float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding() 888 + mNotificationStackScroller.getMinStackHeight() 889 + mNotificationStackScroller.getNotificationTopPadding(); 890 float panelHeightQsExpanded = calculatePanelHeightQsExpanded(); 891 float t = (expandedHeight - panelHeightQsCollapsed) 892 / (panelHeightQsExpanded - panelHeightQsCollapsed); 893 894 setQsExpansion(mQsMinExpansionHeight 895 + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight)); 896 } 897 mNotificationStackScroller.setStackHeight(expandedHeight); 898 updateHeader(); 899 updateUnlockIcon(); 900 updateNotificationTranslucency(); 901 } 902 903 /** 904 * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when 905 * collapsing QS / the panel when QS was scrolled 906 */ 907 private int getTempQsMaxExpansion() { 908 int qsTempMaxExpansion = mQsMaxExpansionHeight; 909 if (mScrollYOverride != -1) { 910 qsTempMaxExpansion -= mScrollYOverride; 911 } 912 return qsTempMaxExpansion; 913 } 914 915 private float calculatePanelHeightQsExpanded() { 916 float notificationHeight = mNotificationStackScroller.getHeight() 917 - mNotificationStackScroller.getEmptyBottomMargin() 918 - mNotificationStackScroller.getTopPadding(); 919 float totalHeight = mQsMaxExpansionHeight + notificationHeight 920 + mNotificationStackScroller.getNotificationTopPadding(); 921 if (totalHeight > mNotificationStackScroller.getHeight()) { 922 float fullyCollapsedHeight = mQsMaxExpansionHeight 923 + mNotificationStackScroller.getMinStackHeight() 924 + mNotificationStackScroller.getNotificationTopPadding() 925 - getScrollViewScrollY(); 926 totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight()); 927 } 928 return totalHeight; 929 } 930 931 private int getScrollViewScrollY() { 932 if (mScrollYOverride != -1) { 933 return mScrollYOverride; 934 } else { 935 return mScrollView.getScrollY(); 936 } 937 } 938 private void updateNotificationTranslucency() { 939 float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight()) 940 / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() 941 + mNotificationStackScroller.getCollapseSecondCardPadding()); 942 alpha = Math.max(0, Math.min(alpha, 1)); 943 alpha = (float) Math.pow(alpha, 0.75); 944 if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) { 945 mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null); 946 } else if (alpha == 1f 947 && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) { 948 mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null); 949 } 950 mNotificationStackScroller.setAlpha(alpha); 951 } 952 953 @Override 954 protected float getOverExpansionAmount() { 955 return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */); 956 } 957 958 @Override 959 protected float getOverExpansionPixels() { 960 return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */); 961 } 962 963 private void updateUnlockIcon() { 964 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 965 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 966 boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance; 967 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 968 if (active && !mUnlockIconActive && mTracking) { 969 lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null); 970 lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150, 971 mFastOutLinearInterpolator); 972 } else if (!active && mUnlockIconActive && mTracking) { 973 lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true, 974 150, mFastOutLinearInterpolator, null); 975 lockIcon.setImageScale(1.0f, true, 150, 976 mFastOutLinearInterpolator); 977 } 978 mUnlockIconActive = active; 979 } 980 } 981 982 /** 983 * Hides the header when notifications are colliding with it. 984 */ 985 private void updateHeader() { 986 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 987 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 988 updateHeaderKeyguard(); 989 } else { 990 updateHeaderShade(); 991 } 992 993 } 994 995 private void updateHeaderShade() { 996 mHeader.setAlpha(1f); 997 mHeader.setTranslationY(getHeaderTranslation()); 998 setQsTranslation(mQsExpansionHeight); 999 } 1000 1001 private float getHeaderTranslation() { 1002 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1003 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1004 return 0; 1005 } 1006 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1007 if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) { 1008 return 0; 1009 } else { 1010 return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight; 1011 } 1012 } 1013 return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR; 1014 } 1015 1016 private void updateHeaderKeyguard() { 1017 mHeader.setTranslationY(0f); 1018 float alpha; 1019 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1020 1021 // When on Keyguard, we hide the header as soon as the top card of the notification 1022 // stack scroller is close enough (collision distance) to the bottom of the header. 1023 alpha = getNotificationsTopY() 1024 / 1025 (mQsMinExpansionHeight + mNotificationsHeaderCollideDistance); 1026 1027 } else { 1028 1029 // In SHADE_LOCKED, the top card is already really close to the header. Hide it as 1030 // soon as we start translating the stack. 1031 alpha = getNotificationsTopY() / mQsMinExpansionHeight; 1032 } 1033 alpha = Math.max(0, Math.min(alpha, 1)); 1034 alpha = (float) Math.pow(alpha, 0.75); 1035 mHeader.setAlpha(alpha); 1036 mKeyguardBottomArea.setAlpha(alpha); 1037 setQsTranslation(mQsExpansionHeight); 1038 } 1039 1040 private float getNotificationsTopY() { 1041 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1042 return getExpandedHeight(); 1043 } 1044 return mNotificationStackScroller.getNotificationsTopY(); 1045 } 1046 1047 @Override 1048 protected void onExpandingStarted() { 1049 super.onExpandingStarted(); 1050 mNotificationStackScroller.onExpansionStarted(); 1051 mIsExpanding = true; 1052 mQsExpandedWhenExpandingStarted = mQsExpanded; 1053 if (mQsExpanded) { 1054 onQsExpansionStarted(); 1055 } 1056 } 1057 1058 @Override 1059 protected void onExpandingFinished() { 1060 super.onExpandingFinished(); 1061 mNotificationStackScroller.onExpansionStopped(); 1062 mIsExpanding = false; 1063 mScrollYOverride = -1; 1064 if (mExpandedHeight == 0f) { 1065 mHeader.setListening(false); 1066 mQsPanel.setListening(false); 1067 } else { 1068 mHeader.setListening(true); 1069 mQsPanel.setListening(true); 1070 } 1071 mTwoFingerQsExpand = false; 1072 mTwoFingerQsExpandPossible = false; 1073 } 1074 1075 @Override 1076 public void instantExpand() { 1077 super.instantExpand(); 1078 mHeader.setListening(true); 1079 mQsPanel.setListening(true); 1080 } 1081 1082 @Override 1083 protected void setOverExpansion(float overExpansion, boolean isPixels) { 1084 if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) { 1085 return; 1086 } 1087 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 1088 mNotificationStackScroller.setOnHeightChangedListener(null); 1089 if (isPixels) { 1090 mNotificationStackScroller.setOverScrolledPixels( 1091 overExpansion, true /* onTop */, false /* animate */); 1092 } else { 1093 mNotificationStackScroller.setOverScrollAmount( 1094 overExpansion, true /* onTop */, false /* animate */); 1095 } 1096 mNotificationStackScroller.setOnHeightChangedListener(this); 1097 } 1098 } 1099 1100 @Override 1101 protected void onTrackingStarted() { 1102 super.onTrackingStarted(); 1103 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1104 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1105 mAfforanceHelper.animateHideLeftRightIcon(); 1106 } 1107 } 1108 1109 @Override 1110 protected void onTrackingStopped(boolean expand) { 1111 super.onTrackingStopped(expand); 1112 if (expand) { 1113 mNotificationStackScroller.setOverScrolledPixels( 1114 0.0f, true /* onTop */, true /* animate */); 1115 } 1116 if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1117 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1118 if (!mHintAnimationRunning) { 1119 mAfforanceHelper.reset(true); 1120 } 1121 } 1122 if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1123 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1124 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1125 lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null); 1126 lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator); 1127 } 1128 } 1129 1130 @Override 1131 public void onHeightChanged(ExpandableView view) { 1132 1133 // Block update if we are in quick settings and just the top padding changed 1134 // (i.e. view == null). 1135 if (view == null && mQsExpanded) { 1136 return; 1137 } 1138 requestPanelHeightUpdate(); 1139 } 1140 1141 @Override 1142 public void onScrollChanged() { 1143 if (mQsExpanded) { 1144 requestScrollerTopPaddingUpdate(false /* animate */); 1145 requestPanelHeightUpdate(); 1146 } 1147 } 1148 1149 @Override 1150 protected void onConfigurationChanged(Configuration newConfig) { 1151 super.onConfigurationChanged(newConfig); 1152 mAfforanceHelper.onConfigurationChanged(); 1153 } 1154 1155 @Override 1156 public void onClick(View v) { 1157 if (v == mHeader) { 1158 onQsExpansionStarted(); 1159 if (mQsExpanded) { 1160 flingSettings(0 /* vel */, false /* expand */); 1161 } else if (mQsExpansionEnabled) { 1162 flingSettings(0 /* vel */, true /* expand */); 1163 } 1164 } 1165 } 1166 1167 @Override 1168 public void onAnimationToSideStarted(boolean rightPage) { 1169 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage; 1170 mIsLaunchTransitionRunning = true; 1171 mLaunchAnimationEndRunnable = null; 1172 if (start) { 1173 mKeyguardBottomArea.launchPhone(); 1174 } else { 1175 mKeyguardBottomArea.launchCamera(); 1176 } 1177 mBlockTouches = true; 1178 } 1179 1180 @Override 1181 public void onAnimationToSideEnded() { 1182 mIsLaunchTransitionRunning = false; 1183 mIsLaunchTransitionFinished = true; 1184 if (mLaunchAnimationEndRunnable != null) { 1185 mLaunchAnimationEndRunnable.run(); 1186 mLaunchAnimationEndRunnable = null; 1187 } 1188 } 1189 1190 @Override 1191 protected void onEdgeClicked(boolean right) { 1192 if ((right && getRightIcon().getVisibility() != View.VISIBLE) 1193 || (!right && getLeftIcon().getVisibility() != View.VISIBLE)) { 1194 return; 1195 } 1196 mHintAnimationRunning = true; 1197 mAfforanceHelper.startHintAnimation(right, new Runnable() { 1198 @Override 1199 public void run() { 1200 mHintAnimationRunning = false; 1201 mStatusBar.onHintFinished(); 1202 } 1203 }); 1204 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right; 1205 if (start) { 1206 mStatusBar.onPhoneHintStarted(); 1207 } else { 1208 mStatusBar.onCameraHintStarted(); 1209 } 1210 } 1211 1212 @Override 1213 protected void startUnlockHintAnimation() { 1214 super.startUnlockHintAnimation(); 1215 startHighlightIconAnimation(getCenterIcon()); 1216 } 1217 1218 /** 1219 * Starts the highlight (making it fully opaque) animation on an icon. 1220 */ 1221 private void startHighlightIconAnimation(final KeyguardAffordanceView icon) { 1222 icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1223 mFastOutSlowInInterpolator, new Runnable() { 1224 @Override 1225 public void run() { 1226 icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, 1227 true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1228 mFastOutSlowInInterpolator, null); 1229 } 1230 }); 1231 } 1232 1233 @Override 1234 public float getPageWidth() { 1235 return getWidth(); 1236 } 1237 1238 @Override 1239 public void onSwipingStarted() { 1240 requestDisallowInterceptTouchEvent(true); 1241 mOnlyAffordanceInThisMotion = true; 1242 } 1243 1244 @Override 1245 public KeyguardAffordanceView getLeftIcon() { 1246 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1247 ? mKeyguardBottomArea.getCameraView() 1248 : mKeyguardBottomArea.getPhoneView(); 1249 } 1250 1251 @Override 1252 public KeyguardAffordanceView getCenterIcon() { 1253 return mKeyguardBottomArea.getLockIcon(); 1254 } 1255 1256 @Override 1257 public KeyguardAffordanceView getRightIcon() { 1258 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1259 ? mKeyguardBottomArea.getPhoneView() 1260 : mKeyguardBottomArea.getCameraView(); 1261 } 1262 1263 @Override 1264 public View getLeftPreview() { 1265 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1266 ? mKeyguardBottomArea.getCameraPreview() 1267 : mKeyguardBottomArea.getPhonePreview(); 1268 } 1269 1270 @Override 1271 public View getRightPreview() { 1272 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1273 ? mKeyguardBottomArea.getPhonePreview() 1274 : mKeyguardBottomArea.getCameraPreview(); 1275 } 1276 1277 @Override 1278 protected float getPeekHeight() { 1279 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 1280 return mNotificationStackScroller.getPeekHeight(); 1281 } else { 1282 return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR; 1283 } 1284 } 1285 1286 @Override 1287 protected float getCannedFlingDurationFactor() { 1288 if (mQsExpanded) { 1289 return 0.7f; 1290 } else { 1291 return 0.6f; 1292 } 1293 } 1294 1295 @Override 1296 protected boolean fullyExpandedClearAllVisible() { 1297 return mNotificationStackScroller.isDismissViewNotGone() 1298 && mNotificationStackScroller.isScrolledToBottom(); 1299 } 1300 1301 @Override 1302 protected boolean isClearAllVisible() { 1303 return mNotificationStackScroller.isDismissViewVisible(); 1304 } 1305 1306 @Override 1307 protected int getClearAllHeight() { 1308 return mNotificationStackScroller.getDismissViewHeight(); 1309 } 1310 1311 @Override 1312 protected boolean isTrackingBlocked() { 1313 return mConflictingQsExpansionGesture && mQsExpanded; 1314 } 1315 1316 public void notifyVisibleChildrenChanged() { 1317 if (mNotificationStackScroller.getNotGoneChildCount() != 0) { 1318 mReserveNotificationSpace.setVisibility(View.VISIBLE); 1319 } else { 1320 mReserveNotificationSpace.setVisibility(View.GONE); 1321 } 1322 } 1323 1324 public boolean isQsExpanded() { 1325 return mQsExpanded; 1326 } 1327 1328 @Override 1329 public boolean shouldDelayChildPressedState() { 1330 return true; 1331 } 1332 1333 public boolean isLaunchTransitionFinished() { 1334 return mIsLaunchTransitionFinished; 1335 } 1336 1337 public boolean isLaunchTransitionRunning() { 1338 return mIsLaunchTransitionRunning; 1339 } 1340 1341 public void setLaunchTransitionEndRunnable(Runnable r) { 1342 mLaunchAnimationEndRunnable = r; 1343 } 1344 1345 public void setEmptyDragAmount(float amount) { 1346 float factor = 1f; 1347 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 1348 factor = 0.6f; 1349 } else if (!mStatusBar.hasNotifications()) { 1350 factor = 0.4f; 1351 } 1352 mEmptyDragAmount = amount * factor; 1353 positionClockAndNotifications(); 1354 } 1355} 1356