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