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