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