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