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