NotificationPanelView.java revision 48bc36af053885daf091bcca6d99411e0438ba83
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 setQsExpansion(mQsMinExpansionHeight + rounded); 628 updateQsState(); 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.getItemHeight() / 2); 941 alpha = Math.max(0, Math.min(alpha, 1)); 942 alpha = (float) Math.pow(alpha, 0.75); 943 944 // TODO: Draw a rect with DST_OUT over the notifications to achieve the same effect - 945 // this would be much more efficient. 946 mNotificationStackScroller.setAlpha(alpha); 947 } 948 949 @Override 950 protected float getOverExpansionAmount() { 951 return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */); 952 } 953 954 @Override 955 protected float getOverExpansionPixels() { 956 return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */); 957 } 958 959 private void updateUnlockIcon() { 960 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 961 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 962 boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance; 963 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 964 if (active && !mUnlockIconActive && mTracking) { 965 lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null); 966 lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150, 967 mFastOutLinearInterpolator); 968 } else if (!active && mUnlockIconActive && mTracking) { 969 lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true, 970 150, mFastOutLinearInterpolator, null); 971 lockIcon.setImageScale(1.0f, true, 150, 972 mFastOutLinearInterpolator); 973 } 974 mUnlockIconActive = active; 975 } 976 } 977 978 /** 979 * Hides the header when notifications are colliding with it. 980 */ 981 private void updateHeader() { 982 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 983 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 984 updateHeaderKeyguard(); 985 } else { 986 updateHeaderShade(); 987 } 988 989 } 990 991 private void updateHeaderShade() { 992 mHeader.setAlpha(1f); 993 mHeader.setTranslationY(getHeaderTranslation()); 994 setQsTranslation(mQsExpansionHeight); 995 } 996 997 private float getHeaderTranslation() { 998 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 999 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1000 return 0; 1001 } 1002 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1003 if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) { 1004 return 0; 1005 } else { 1006 return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight; 1007 } 1008 } 1009 return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR; 1010 } 1011 1012 private void updateHeaderKeyguard() { 1013 mHeader.setTranslationY(0f); 1014 float alpha; 1015 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1016 1017 // When on Keyguard, we hide the header as soon as the top card of the notification 1018 // stack scroller is close enough (collision distance) to the bottom of the header. 1019 alpha = getNotificationsTopY() 1020 / 1021 (mQsMinExpansionHeight + mNotificationsHeaderCollideDistance); 1022 1023 } else { 1024 1025 // In SHADE_LOCKED, the top card is already really close to the header. Hide it as 1026 // soon as we start translating the stack. 1027 alpha = getNotificationsTopY() / mQsMinExpansionHeight; 1028 } 1029 alpha = Math.max(0, Math.min(alpha, 1)); 1030 alpha = (float) Math.pow(alpha, 0.75); 1031 mHeader.setAlpha(alpha); 1032 mKeyguardBottomArea.setAlpha(alpha); 1033 setQsTranslation(mQsExpansionHeight); 1034 } 1035 1036 private float getNotificationsTopY() { 1037 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1038 return getExpandedHeight(); 1039 } 1040 return mNotificationStackScroller.getNotificationsTopY(); 1041 } 1042 1043 @Override 1044 protected void onExpandingStarted() { 1045 super.onExpandingStarted(); 1046 mNotificationStackScroller.onExpansionStarted(); 1047 mIsExpanding = true; 1048 mQsExpandedWhenExpandingStarted = mQsExpanded; 1049 if (mQsExpanded) { 1050 onQsExpansionStarted(); 1051 } 1052 } 1053 1054 @Override 1055 protected void onExpandingFinished() { 1056 super.onExpandingFinished(); 1057 mNotificationStackScroller.onExpansionStopped(); 1058 mIsExpanding = false; 1059 mScrollYOverride = -1; 1060 if (mExpandedHeight == 0f) { 1061 mHeader.setListening(false); 1062 mQsPanel.setListening(false); 1063 } else { 1064 mHeader.setListening(true); 1065 mQsPanel.setListening(true); 1066 } 1067 mTwoFingerQsExpand = false; 1068 mTwoFingerQsExpandPossible = false; 1069 } 1070 1071 @Override 1072 public void instantExpand() { 1073 super.instantExpand(); 1074 mHeader.setListening(true); 1075 mQsPanel.setListening(true); 1076 } 1077 1078 @Override 1079 protected void setOverExpansion(float overExpansion, boolean isPixels) { 1080 if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) { 1081 return; 1082 } 1083 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 1084 mNotificationStackScroller.setOnHeightChangedListener(null); 1085 if (isPixels) { 1086 mNotificationStackScroller.setOverScrolledPixels( 1087 overExpansion, true /* onTop */, false /* animate */); 1088 } else { 1089 mNotificationStackScroller.setOverScrollAmount( 1090 overExpansion, true /* onTop */, false /* animate */); 1091 } 1092 mNotificationStackScroller.setOnHeightChangedListener(this); 1093 } 1094 } 1095 1096 @Override 1097 protected void onTrackingStarted() { 1098 super.onTrackingStarted(); 1099 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1100 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1101 mAfforanceHelper.animateHideLeftRightIcon(); 1102 } 1103 } 1104 1105 @Override 1106 protected void onTrackingStopped(boolean expand) { 1107 super.onTrackingStopped(expand); 1108 if (expand) { 1109 mNotificationStackScroller.setOverScrolledPixels( 1110 0.0f, true /* onTop */, true /* animate */); 1111 } 1112 if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1113 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1114 if (!mHintAnimationRunning) { 1115 mAfforanceHelper.reset(true); 1116 } 1117 } 1118 if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1119 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1120 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1121 lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null); 1122 lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator); 1123 } 1124 } 1125 1126 @Override 1127 public void onHeightChanged(ExpandableView view) { 1128 1129 // Block update if we are in quick settings and just the top padding changed 1130 // (i.e. view == null). 1131 if (view == null && mQsExpanded) { 1132 return; 1133 } 1134 requestPanelHeightUpdate(); 1135 } 1136 1137 @Override 1138 public void onScrollChanged() { 1139 if (mQsExpanded) { 1140 requestScrollerTopPaddingUpdate(false /* animate */); 1141 requestPanelHeightUpdate(); 1142 } 1143 } 1144 1145 @Override 1146 protected void onConfigurationChanged(Configuration newConfig) { 1147 super.onConfigurationChanged(newConfig); 1148 mAfforanceHelper.onConfigurationChanged(); 1149 } 1150 1151 @Override 1152 public void onClick(View v) { 1153 if (v == mHeader) { 1154 onQsExpansionStarted(); 1155 if (mQsExpanded) { 1156 flingSettings(0 /* vel */, false /* expand */); 1157 } else if (mQsExpansionEnabled) { 1158 flingSettings(0 /* vel */, true /* expand */); 1159 } 1160 } 1161 } 1162 1163 @Override 1164 public void onAnimationToSideStarted(boolean rightPage) { 1165 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage; 1166 mIsLaunchTransitionRunning = true; 1167 mLaunchAnimationEndRunnable = null; 1168 if (start) { 1169 mKeyguardBottomArea.launchPhone(); 1170 } else { 1171 mKeyguardBottomArea.launchCamera(); 1172 } 1173 mBlockTouches = true; 1174 } 1175 1176 @Override 1177 public void onAnimationToSideEnded() { 1178 mIsLaunchTransitionRunning = false; 1179 mIsLaunchTransitionFinished = true; 1180 if (mLaunchAnimationEndRunnable != null) { 1181 mLaunchAnimationEndRunnable.run(); 1182 mLaunchAnimationEndRunnable = null; 1183 } 1184 } 1185 1186 @Override 1187 protected void onEdgeClicked(boolean right) { 1188 if ((right && getRightIcon().getVisibility() != View.VISIBLE) 1189 || (!right && getLeftIcon().getVisibility() != View.VISIBLE)) { 1190 return; 1191 } 1192 mHintAnimationRunning = true; 1193 mAfforanceHelper.startHintAnimation(right, new Runnable() { 1194 @Override 1195 public void run() { 1196 mHintAnimationRunning = false; 1197 mStatusBar.onHintFinished(); 1198 } 1199 }); 1200 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right; 1201 if (start) { 1202 mStatusBar.onPhoneHintStarted(); 1203 } else { 1204 mStatusBar.onCameraHintStarted(); 1205 } 1206 } 1207 1208 @Override 1209 protected void startUnlockHintAnimation() { 1210 super.startUnlockHintAnimation(); 1211 startHighlightIconAnimation(getCenterIcon()); 1212 } 1213 1214 /** 1215 * Starts the highlight (making it fully opaque) animation on an icon. 1216 */ 1217 private void startHighlightIconAnimation(final KeyguardAffordanceView icon) { 1218 icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1219 mFastOutSlowInInterpolator, new Runnable() { 1220 @Override 1221 public void run() { 1222 icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, 1223 true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1224 mFastOutSlowInInterpolator, null); 1225 } 1226 }); 1227 } 1228 1229 @Override 1230 public float getPageWidth() { 1231 return getWidth(); 1232 } 1233 1234 @Override 1235 public void onSwipingStarted() { 1236 requestDisallowInterceptTouchEvent(true); 1237 mOnlyAffordanceInThisMotion = true; 1238 } 1239 1240 @Override 1241 public KeyguardAffordanceView getLeftIcon() { 1242 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1243 ? mKeyguardBottomArea.getCameraView() 1244 : mKeyguardBottomArea.getPhoneView(); 1245 } 1246 1247 @Override 1248 public KeyguardAffordanceView getCenterIcon() { 1249 return mKeyguardBottomArea.getLockIcon(); 1250 } 1251 1252 @Override 1253 public KeyguardAffordanceView getRightIcon() { 1254 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1255 ? mKeyguardBottomArea.getPhoneView() 1256 : mKeyguardBottomArea.getCameraView(); 1257 } 1258 1259 @Override 1260 public View getLeftPreview() { 1261 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1262 ? mKeyguardBottomArea.getCameraPreview() 1263 : mKeyguardBottomArea.getPhonePreview(); 1264 } 1265 1266 @Override 1267 public View getRightPreview() { 1268 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1269 ? mKeyguardBottomArea.getPhonePreview() 1270 : mKeyguardBottomArea.getCameraPreview(); 1271 } 1272 1273 @Override 1274 protected float getPeekHeight() { 1275 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 1276 return mNotificationStackScroller.getPeekHeight(); 1277 } else { 1278 return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR; 1279 } 1280 } 1281 1282 @Override 1283 protected float getCannedFlingDurationFactor() { 1284 if (mQsExpanded) { 1285 return 0.7f; 1286 } else { 1287 return 0.6f; 1288 } 1289 } 1290 1291 @Override 1292 protected boolean isTrackingBlocked() { 1293 return mConflictingQsExpansionGesture && mQsExpanded; 1294 } 1295 1296 public void notifyVisibleChildrenChanged() { 1297 if (mNotificationStackScroller.getNotGoneChildCount() != 0) { 1298 mReserveNotificationSpace.setVisibility(View.VISIBLE); 1299 } else { 1300 mReserveNotificationSpace.setVisibility(View.GONE); 1301 } 1302 } 1303 1304 public boolean isQsExpanded() { 1305 return mQsExpanded; 1306 } 1307 1308 @Override 1309 public boolean shouldDelayChildPressedState() { 1310 return true; 1311 } 1312 1313 public boolean isLaunchTransitionFinished() { 1314 return mIsLaunchTransitionFinished; 1315 } 1316 1317 public boolean isLaunchTransitionRunning() { 1318 return mIsLaunchTransitionRunning; 1319 } 1320 1321 public void setLaunchTransitionEndRunnable(Runnable r) { 1322 mLaunchAnimationEndRunnable = r; 1323 } 1324 1325 public void setEmptyDragAmount(float amount) { 1326 float factor = 1f; 1327 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 1328 factor = 0.6f; 1329 } else if (!mStatusBar.hasNotifications()) { 1330 factor = 0.4f; 1331 } 1332 mEmptyDragAmount = amount * factor; 1333 positionClockAndNotifications(); 1334 } 1335} 1336