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