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