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 17 package com.android.systemui.statusbar.phone; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.PropertyValuesHolder; 23 import android.animation.ValueAnimator; 24 import android.content.Context; 25 import android.content.res.Configuration; 26 import android.graphics.Canvas; 27 import android.graphics.Color; 28 import android.graphics.Paint; 29 import android.graphics.Rect; 30 import android.util.AttributeSet; 31 import android.util.MathUtils; 32 import android.view.MotionEvent; 33 import android.view.VelocityTracker; 34 import android.view.View; 35 import android.view.ViewTreeObserver; 36 import android.view.WindowInsets; 37 import android.view.accessibility.AccessibilityEvent; 38 import android.view.animation.AnimationUtils; 39 import android.view.animation.Interpolator; 40 import android.view.animation.PathInterpolator; 41 import android.widget.FrameLayout; 42 import android.widget.TextView; 43 44 import com.android.internal.logging.MetricsLogger; 45 import com.android.keyguard.KeyguardStatusView; 46 import com.android.systemui.DejankUtils; 47 import com.android.systemui.EventLogConstants; 48 import com.android.systemui.EventLogTags; 49 import com.android.systemui.R; 50 import com.android.systemui.qs.QSContainer; 51 import com.android.systemui.qs.QSPanel; 52 import com.android.systemui.statusbar.ExpandableNotificationRow; 53 import com.android.systemui.statusbar.ExpandableView; 54 import com.android.systemui.statusbar.FlingAnimationUtils; 55 import com.android.systemui.statusbar.GestureRecorder; 56 import com.android.systemui.statusbar.KeyguardAffordanceView; 57 import com.android.systemui.statusbar.NotificationData; 58 import com.android.systemui.statusbar.StatusBarState; 59 import com.android.systemui.statusbar.policy.HeadsUpManager; 60 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; 61 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 62 import com.android.systemui.statusbar.stack.StackStateAnimator; 63 64 public class NotificationPanelView extends PanelView implements 65 ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, 66 View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener, 67 KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener, 68 HeadsUpManager.OnHeadsUpChangedListener { 69 70 private static final boolean DEBUG = false; 71 72 // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is 73 // changed. 74 private static final int CAP_HEIGHT = 1456; 75 private static final int FONT_HEIGHT = 2163; 76 77 private static final float HEADER_RUBBERBAND_FACTOR = 2.05f; 78 private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f; 79 80 private static final String COUNTER_PANEL_OPEN = "panel_open"; 81 private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs"; 82 private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek"; 83 84 private static final Rect mDummyDirtyRect = new Rect(0, 0, 1, 1); 85 86 public static final long DOZE_ANIMATION_DURATION = 700; 87 88 private KeyguardAffordanceHelper mAfforanceHelper; 89 private StatusBarHeaderView mHeader; 90 private KeyguardUserSwitcher mKeyguardUserSwitcher; 91 private KeyguardStatusBarView mKeyguardStatusBar; 92 private QSContainer mQsContainer; 93 private QSPanel mQsPanel; 94 private KeyguardStatusView mKeyguardStatusView; 95 private ObservableScrollView mScrollView; 96 private TextView mClockView; 97 private View mReserveNotificationSpace; 98 private View mQsNavbarScrim; 99 private NotificationsQuickSettingsContainer mNotificationContainerParent; 100 private NotificationStackScrollLayout mNotificationStackScroller; 101 private int mNotificationTopPadding; 102 private boolean mAnimateNextTopPaddingChange; 103 104 private int mTrackingPointer; 105 private VelocityTracker mVelocityTracker; 106 private boolean mQsTracking; 107 108 /** 109 * Handles launching the secure camera properly even when other applications may be using the 110 * camera hardware. 111 */ 112 private SecureCameraLaunchManager mSecureCameraLaunchManager; 113 114 /** 115 * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and 116 * the expansion for quick settings. 117 */ 118 private boolean mConflictingQsExpansionGesture; 119 120 /** 121 * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't 122 * intercepted yet. 123 */ 124 private boolean mIntercepting; 125 private boolean mPanelExpanded; 126 private boolean mQsExpanded; 127 private boolean mQsExpandedWhenExpandingStarted; 128 private boolean mQsFullyExpanded; 129 private boolean mKeyguardShowing; 130 private boolean mDozing; 131 private boolean mDozingOnDown; 132 private int mStatusBarState; 133 private float mInitialHeightOnTouch; 134 private float mInitialTouchX; 135 private float mInitialTouchY; 136 private float mLastTouchX; 137 private float mLastTouchY; 138 private float mQsExpansionHeight; 139 private int mQsMinExpansionHeight; 140 private int mQsMaxExpansionHeight; 141 private int mQsPeekHeight; 142 private boolean mStackScrollerOverscrolling; 143 private boolean mQsExpansionFromOverscroll; 144 private float mLastOverscroll; 145 private boolean mQsExpansionEnabled = true; 146 private ValueAnimator mQsExpansionAnimator; 147 private FlingAnimationUtils mFlingAnimationUtils; 148 private int mStatusBarMinHeight; 149 private boolean mUnlockIconActive; 150 private int mNotificationsHeaderCollideDistance; 151 private int mUnlockMoveDistance; 152 private float mEmptyDragAmount; 153 154 private Interpolator mFastOutSlowInInterpolator; 155 private Interpolator mFastOutLinearInterpolator; 156 private Interpolator mDozeAnimationInterpolator; 157 private ObjectAnimator mClockAnimator; 158 private int mClockAnimationTarget = -1; 159 private int mTopPaddingAdjustment; 160 private KeyguardClockPositionAlgorithm mClockPositionAlgorithm = 161 new KeyguardClockPositionAlgorithm(); 162 private KeyguardClockPositionAlgorithm.Result mClockPositionResult = 163 new KeyguardClockPositionAlgorithm.Result(); 164 private boolean mIsExpanding; 165 166 private boolean mBlockTouches; 167 private int mNotificationScrimWaitDistance; 168 // Used for two finger gesture as well as accessibility shortcut to QS. 169 private boolean mQsExpandImmediate; 170 private boolean mTwoFingerQsExpandPossible; 171 172 /** 173 * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still 174 * need to take this into account in our panel height calculation. 175 */ 176 private int mScrollYOverride = -1; 177 private boolean mQsAnimatorExpand; 178 private boolean mIsLaunchTransitionFinished; 179 private boolean mIsLaunchTransitionRunning; 180 private Runnable mLaunchAnimationEndRunnable; 181 private boolean mOnlyAffordanceInThisMotion; 182 private boolean mKeyguardStatusViewAnimating; 183 private boolean mHeaderAnimating; 184 private ObjectAnimator mQsContainerAnimator; 185 private ValueAnimator mQsSizeChangeAnimator; 186 187 private boolean mShadeEmpty; 188 189 private boolean mQsScrimEnabled = true; 190 private boolean mLastAnnouncementWasQuickSettings; 191 private boolean mQsTouchAboveFalsingThreshold; 192 private int mQsFalsingThreshold; 193 194 private float mKeyguardStatusBarAnimateAlpha = 1f; 195 private int mOldLayoutDirection; 196 private HeadsUpTouchHelper mHeadsUpTouchHelper; 197 private boolean mIsExpansionFromHeadsUp; 198 private boolean mListenForHeadsUp; 199 private int mNavigationBarBottomHeight; 200 private boolean mExpandingFromHeadsUp; 201 private boolean mCollapsedOnDown; 202 private int mPositionMinSideMargin; 203 private int mLastOrientation = -1; 204 private boolean mClosingWithAlphaFadeOut; 205 private boolean mHeadsUpAnimatingAway; 206 207 private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() { 208 @Override 209 public void run() { 210 mHeadsUpAnimatingAway = false; 211 notifyBarPanelExpansionChanged(); 212 } 213 }; 214 215 /** Interpolator to be used for animations that respond directly to a touch */ 216 private final Interpolator mTouchResponseInterpolator = 217 new PathInterpolator(0.3f, 0f, 0.1f, 1f); 218 NotificationPanelView(Context context, AttributeSet attrs)219 public NotificationPanelView(Context context, AttributeSet attrs) { 220 super(context, attrs); 221 setWillNotDraw(!DEBUG); 222 } 223 setStatusBar(PhoneStatusBar bar)224 public void setStatusBar(PhoneStatusBar bar) { 225 mStatusBar = bar; 226 } 227 228 @Override onFinishInflate()229 protected void onFinishInflate() { 230 super.onFinishInflate(); 231 mHeader = (StatusBarHeaderView) findViewById(R.id.header); 232 mHeader.setOnClickListener(this); 233 mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header); 234 mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view); 235 mQsContainer = (QSContainer) findViewById(R.id.quick_settings_container); 236 mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel); 237 mClockView = (TextView) findViewById(R.id.clock_view); 238 mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view); 239 mScrollView.setListener(this); 240 mScrollView.setFocusable(false); 241 mReserveNotificationSpace = findViewById(R.id.reserve_notification_space); 242 mNotificationContainerParent = (NotificationsQuickSettingsContainer) 243 findViewById(R.id.notification_container_parent); 244 mNotificationStackScroller = (NotificationStackScrollLayout) 245 findViewById(R.id.notification_stack_scroller); 246 mNotificationStackScroller.setOnHeightChangedListener(this); 247 mNotificationStackScroller.setOverscrollTopChangedListener(this); 248 mNotificationStackScroller.setOnEmptySpaceClickListener(this); 249 mNotificationStackScroller.setScrollView(mScrollView); 250 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), 251 android.R.interpolator.fast_out_slow_in); 252 mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(), 253 android.R.interpolator.fast_out_linear_in); 254 mDozeAnimationInterpolator = AnimationUtils.loadInterpolator(getContext(), 255 android.R.interpolator.linear_out_slow_in); 256 mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); 257 mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim); 258 mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext()); 259 mSecureCameraLaunchManager = 260 new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea); 261 mLastOrientation = getResources().getConfiguration().orientation; 262 263 // recompute internal state when qspanel height changes 264 mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() { 265 @Override 266 public void onLayoutChange(View v, int left, int top, int right, int bottom, 267 int oldLeft, int oldTop, int oldRight, int oldBottom) { 268 final int height = bottom - top; 269 final int oldHeight = oldBottom - oldTop; 270 if (height != oldHeight) { 271 onScrollChanged(); 272 } 273 } 274 }); 275 } 276 277 @Override loadDimens()278 protected void loadDimens() { 279 super.loadDimens(); 280 mNotificationTopPadding = getResources().getDimensionPixelSize( 281 R.dimen.notifications_top_padding); 282 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f); 283 mStatusBarMinHeight = getResources().getDimensionPixelSize( 284 com.android.internal.R.dimen.status_bar_height); 285 mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height); 286 mNotificationsHeaderCollideDistance = 287 getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance); 288 mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance); 289 mClockPositionAlgorithm.loadDimens(getResources()); 290 mNotificationScrimWaitDistance = 291 getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance); 292 mQsFalsingThreshold = getResources().getDimensionPixelSize( 293 R.dimen.qs_falsing_threshold); 294 mPositionMinSideMargin = getResources().getDimensionPixelSize( 295 R.dimen.notification_panel_min_side_margin); 296 } 297 updateResources()298 public void updateResources() { 299 int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width); 300 int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity); 301 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mHeader.getLayoutParams(); 302 if (lp.width != panelWidth) { 303 lp.width = panelWidth; 304 lp.gravity = panelGravity; 305 mHeader.setLayoutParams(lp); 306 mHeader.post(mUpdateHeader); 307 } 308 309 lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams(); 310 if (lp.width != panelWidth) { 311 lp.width = panelWidth; 312 lp.gravity = panelGravity; 313 mNotificationStackScroller.setLayoutParams(lp); 314 } 315 316 lp = (FrameLayout.LayoutParams) mScrollView.getLayoutParams(); 317 if (lp.width != panelWidth) { 318 lp.width = panelWidth; 319 lp.gravity = panelGravity; 320 mScrollView.setLayoutParams(lp); 321 } 322 } 323 324 @Override onLayout(boolean changed, int left, int top, int right, int bottom)325 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 326 super.onLayout(changed, left, top, right, bottom); 327 328 // Update Clock Pivot 329 mKeyguardStatusView.setPivotX(getWidth() / 2); 330 mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize()); 331 332 // Calculate quick setting heights. 333 int oldMaxHeight = mQsMaxExpansionHeight; 334 mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight; 335 mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getDesiredHeight(); 336 positionClockAndNotifications(); 337 if (mQsExpanded && mQsFullyExpanded) { 338 mQsExpansionHeight = mQsMaxExpansionHeight; 339 requestScrollerTopPaddingUpdate(false /* animate */); 340 requestPanelHeightUpdate(); 341 342 // Size has changed, start an animation. 343 if (mQsMaxExpansionHeight != oldMaxHeight) { 344 startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight); 345 } 346 } else if (!mQsExpanded) { 347 setQsExpansion(mQsMinExpansionHeight + mLastOverscroll); 348 } 349 updateStackHeight(getExpandedHeight()); 350 updateHeader(); 351 mNotificationStackScroller.updateIsSmallScreen( 352 mHeader.getCollapsedHeight() + mQsPeekHeight); 353 354 // If we are running a size change animation, the animation takes care of the height of 355 // the container. However, if we are not animating, we always need to make the QS container 356 // the desired height so when closing the QS detail, it stays smaller after the size change 357 // animation is finished but the detail view is still being animated away (this animation 358 // takes longer than the size change animation). 359 if (mQsSizeChangeAnimator == null) { 360 mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight()); 361 } 362 updateMaxHeadsUpTranslation(); 363 } 364 365 @Override onAttachedToWindow()366 public void onAttachedToWindow() { 367 mSecureCameraLaunchManager.create(); 368 } 369 370 @Override onDetachedFromWindow()371 public void onDetachedFromWindow() { 372 mSecureCameraLaunchManager.destroy(); 373 } 374 startQsSizeChangeAnimation(int oldHeight, final int newHeight)375 private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) { 376 if (mQsSizeChangeAnimator != null) { 377 oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue(); 378 mQsSizeChangeAnimator.cancel(); 379 } 380 mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight); 381 mQsSizeChangeAnimator.setDuration(300); 382 mQsSizeChangeAnimator.setInterpolator(mFastOutSlowInInterpolator); 383 mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 384 @Override 385 public void onAnimationUpdate(ValueAnimator animation) { 386 requestScrollerTopPaddingUpdate(false /* animate */); 387 requestPanelHeightUpdate(); 388 int height = (int) mQsSizeChangeAnimator.getAnimatedValue(); 389 mQsContainer.setHeightOverride(height - mHeader.getExpandedHeight()); 390 } 391 }); 392 mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() { 393 @Override 394 public void onAnimationEnd(Animator animation) { 395 mQsSizeChangeAnimator = null; 396 } 397 }); 398 mQsSizeChangeAnimator.start(); 399 } 400 401 /** 402 * Positions the clock and notifications dynamically depending on how many notifications are 403 * showing. 404 */ positionClockAndNotifications()405 private void positionClockAndNotifications() { 406 boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending(); 407 int stackScrollerPadding; 408 if (mStatusBarState != StatusBarState.KEYGUARD) { 409 int bottom = mHeader.getCollapsedHeight(); 410 stackScrollerPadding = mStatusBarState == StatusBarState.SHADE 411 ? bottom + mQsPeekHeight + mNotificationTopPadding 412 : mKeyguardStatusBar.getHeight() + mNotificationTopPadding; 413 mTopPaddingAdjustment = 0; 414 } else { 415 mClockPositionAlgorithm.setup( 416 mStatusBar.getMaxKeyguardNotifications(), 417 getMaxPanelHeight(), 418 getExpandedHeight(), 419 mNotificationStackScroller.getNotGoneChildCount(), 420 getHeight(), 421 mKeyguardStatusView.getHeight(), 422 mEmptyDragAmount); 423 mClockPositionAlgorithm.run(mClockPositionResult); 424 if (animate || mClockAnimator != null) { 425 startClockAnimation(mClockPositionResult.clockY); 426 } else { 427 mKeyguardStatusView.setY(mClockPositionResult.clockY); 428 } 429 updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale); 430 stackScrollerPadding = mClockPositionResult.stackScrollerPadding; 431 mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment; 432 } 433 mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding); 434 requestScrollerTopPaddingUpdate(animate); 435 } 436 startClockAnimation(int y)437 private void startClockAnimation(int y) { 438 if (mClockAnimationTarget == y) { 439 return; 440 } 441 mClockAnimationTarget = y; 442 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 443 @Override 444 public boolean onPreDraw() { 445 getViewTreeObserver().removeOnPreDrawListener(this); 446 if (mClockAnimator != null) { 447 mClockAnimator.removeAllListeners(); 448 mClockAnimator.cancel(); 449 } 450 mClockAnimator = ObjectAnimator 451 .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget); 452 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator); 453 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 454 mClockAnimator.addListener(new AnimatorListenerAdapter() { 455 @Override 456 public void onAnimationEnd(Animator animation) { 457 mClockAnimator = null; 458 mClockAnimationTarget = -1; 459 } 460 }); 461 mClockAnimator.start(); 462 return true; 463 } 464 }); 465 } 466 updateClock(float alpha, float scale)467 private void updateClock(float alpha, float scale) { 468 if (!mKeyguardStatusViewAnimating) { 469 mKeyguardStatusView.setAlpha(alpha); 470 } 471 mKeyguardStatusView.setScaleX(scale); 472 mKeyguardStatusView.setScaleY(scale); 473 } 474 animateToFullShade(long delay)475 public void animateToFullShade(long delay) { 476 mAnimateNextTopPaddingChange = true; 477 mNotificationStackScroller.goToFullShade(delay); 478 requestLayout(); 479 } 480 setQsExpansionEnabled(boolean qsExpansionEnabled)481 public void setQsExpansionEnabled(boolean qsExpansionEnabled) { 482 mQsExpansionEnabled = qsExpansionEnabled; 483 mHeader.setClickable(qsExpansionEnabled); 484 } 485 486 @Override resetViews()487 public void resetViews() { 488 mIsLaunchTransitionFinished = false; 489 mBlockTouches = false; 490 mUnlockIconActive = false; 491 mAfforanceHelper.reset(true); 492 closeQs(); 493 mStatusBar.dismissPopups(); 494 mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */, 495 true /* cancelAnimators */); 496 mNotificationStackScroller.resetScrollPosition(); 497 } 498 closeQs()499 public void closeQs() { 500 cancelQsAnimation(); 501 setQsExpansion(mQsMinExpansionHeight); 502 } 503 animateCloseQs()504 public void animateCloseQs() { 505 if (mQsExpansionAnimator != null) { 506 if (!mQsAnimatorExpand) { 507 return; 508 } 509 float height = mQsExpansionHeight; 510 mQsExpansionAnimator.cancel(); 511 setQsExpansion(height); 512 } 513 flingSettings(0 /* vel */, false); 514 } 515 openQs()516 public void openQs() { 517 cancelQsAnimation(); 518 if (mQsExpansionEnabled) { 519 setQsExpansion(mQsMaxExpansionHeight); 520 } 521 } 522 expandWithQs()523 public void expandWithQs() { 524 if (mQsExpansionEnabled) { 525 mQsExpandImmediate = true; 526 } 527 expand(); 528 } 529 530 @Override fling(float vel, boolean expand)531 public void fling(float vel, boolean expand) { 532 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); 533 if (gr != null) { 534 gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); 535 } 536 super.fling(vel, expand); 537 } 538 539 @Override flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing)540 protected void flingToHeight(float vel, boolean expand, float target, 541 float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { 542 mHeadsUpTouchHelper.notifyFling(!expand); 543 setClosingWithAlphaFadeout(!expand && getFadeoutAlpha() == 1.0f); 544 super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); 545 } 546 547 @Override dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)548 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { 549 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 550 event.getText().add(getKeyguardOrLockScreenString()); 551 mLastAnnouncementWasQuickSettings = false; 552 return true; 553 } 554 return super.dispatchPopulateAccessibilityEventInternal(event); 555 } 556 557 @Override onInterceptTouchEvent(MotionEvent event)558 public boolean onInterceptTouchEvent(MotionEvent event) { 559 if (mBlockTouches) { 560 return false; 561 } 562 initDownStates(event); 563 if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { 564 mIsExpansionFromHeadsUp = true; 565 MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1); 566 MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1); 567 return true; 568 } 569 if (!isFullyCollapsed() && onQsIntercept(event)) { 570 return true; 571 } 572 return super.onInterceptTouchEvent(event); 573 } 574 onQsIntercept(MotionEvent event)575 private boolean onQsIntercept(MotionEvent event) { 576 int pointerIndex = event.findPointerIndex(mTrackingPointer); 577 if (pointerIndex < 0) { 578 pointerIndex = 0; 579 mTrackingPointer = event.getPointerId(pointerIndex); 580 } 581 final float x = event.getX(pointerIndex); 582 final float y = event.getY(pointerIndex); 583 584 switch (event.getActionMasked()) { 585 case MotionEvent.ACTION_DOWN: 586 mIntercepting = true; 587 mInitialTouchY = y; 588 mInitialTouchX = x; 589 initVelocityTracker(); 590 trackMovement(event); 591 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { 592 getParent().requestDisallowInterceptTouchEvent(true); 593 } 594 if (mQsExpansionAnimator != null) { 595 onQsExpansionStarted(); 596 mInitialHeightOnTouch = mQsExpansionHeight; 597 mQsTracking = true; 598 mIntercepting = false; 599 mNotificationStackScroller.removeLongPressCallback(); 600 } 601 break; 602 case MotionEvent.ACTION_POINTER_UP: 603 final int upPointer = event.getPointerId(event.getActionIndex()); 604 if (mTrackingPointer == upPointer) { 605 // gesture is ongoing, find a new pointer to track 606 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 607 mTrackingPointer = event.getPointerId(newIndex); 608 mInitialTouchX = event.getX(newIndex); 609 mInitialTouchY = event.getY(newIndex); 610 } 611 break; 612 613 case MotionEvent.ACTION_MOVE: 614 final float h = y - mInitialTouchY; 615 trackMovement(event); 616 if (mQsTracking) { 617 618 // Already tracking because onOverscrolled was called. We need to update here 619 // so we don't stop for a frame until the next touch event gets handled in 620 // onTouchEvent. 621 setQsExpansion(h + mInitialHeightOnTouch); 622 trackMovement(event); 623 mIntercepting = false; 624 return true; 625 } 626 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) 627 && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { 628 mQsTracking = true; 629 onQsExpansionStarted(); 630 notifyExpandingFinished(); 631 mInitialHeightOnTouch = mQsExpansionHeight; 632 mInitialTouchY = y; 633 mInitialTouchX = x; 634 mIntercepting = false; 635 mNotificationStackScroller.removeLongPressCallback(); 636 return true; 637 } 638 break; 639 640 case MotionEvent.ACTION_CANCEL: 641 case MotionEvent.ACTION_UP: 642 trackMovement(event); 643 if (mQsTracking) { 644 flingQsWithCurrentVelocity(y, 645 event.getActionMasked() == MotionEvent.ACTION_CANCEL); 646 mQsTracking = false; 647 } 648 mIntercepting = false; 649 break; 650 } 651 return false; 652 } 653 654 @Override isInContentBounds(float x, float y)655 protected boolean isInContentBounds(float x, float y) { 656 float stackScrollerX = mNotificationStackScroller.getX(); 657 return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y) 658 && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth(); 659 } 660 initDownStates(MotionEvent event)661 private void initDownStates(MotionEvent event) { 662 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 663 mOnlyAffordanceInThisMotion = false; 664 mQsTouchAboveFalsingThreshold = mQsFullyExpanded; 665 mDozingOnDown = isDozing(); 666 mCollapsedOnDown = isFullyCollapsed(); 667 mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp(); 668 } 669 } 670 671 @Override requestDisallowInterceptTouchEvent(boolean disallowIntercept)672 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 673 674 // Block request when interacting with the scroll view so we can still intercept the 675 // scrolling when QS is expanded. 676 if (mScrollView.isHandlingTouchEvent()) { 677 return; 678 } 679 super.requestDisallowInterceptTouchEvent(disallowIntercept); 680 } 681 flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent)682 private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) { 683 float vel = getCurrentVelocity(); 684 final boolean expandsQs = flingExpandsQs(vel); 685 if (expandsQs) { 686 logQsSwipeDown(y); 687 } 688 flingSettings(vel, expandsQs && !isCancelMotionEvent); 689 } 690 logQsSwipeDown(float y)691 private void logQsSwipeDown(float y) { 692 float vel = getCurrentVelocity(); 693 final int gesture = mStatusBarState == StatusBarState.KEYGUARD 694 ? EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_QS 695 : EventLogConstants.SYSUI_SHADE_GESTURE_SWIPE_DOWN_QS; 696 EventLogTags.writeSysuiLockscreenGesture( 697 gesture, 698 (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()), 699 (int) (vel / mStatusBar.getDisplayDensity())); 700 } 701 flingExpandsQs(float vel)702 private boolean flingExpandsQs(float vel) { 703 if (isBelowFalsingThreshold()) { 704 return false; 705 } 706 if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { 707 return getQsExpansionFraction() > 0.5f; 708 } else { 709 return vel > 0; 710 } 711 } 712 isBelowFalsingThreshold()713 private boolean isBelowFalsingThreshold() { 714 return !mQsTouchAboveFalsingThreshold && mStatusBarState == StatusBarState.KEYGUARD; 715 } 716 getQsExpansionFraction()717 private float getQsExpansionFraction() { 718 return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight) 719 / (getTempQsMaxExpansion() - mQsMinExpansionHeight)); 720 } 721 722 @Override onTouchEvent(MotionEvent event)723 public boolean onTouchEvent(MotionEvent event) { 724 if (mBlockTouches) { 725 return false; 726 } 727 initDownStates(event); 728 if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp() 729 && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { 730 mIsExpansionFromHeadsUp = true; 731 MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1); 732 } 733 if ((!mIsExpanding || mHintAnimationRunning) 734 && !mQsExpanded 735 && mStatusBar.getBarState() != StatusBarState.SHADE) { 736 mAfforanceHelper.onTouchEvent(event); 737 } 738 if (mOnlyAffordanceInThisMotion) { 739 return true; 740 } 741 mHeadsUpTouchHelper.onTouchEvent(event); 742 if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) { 743 return true; 744 } 745 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { 746 MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1); 747 updateVerticalPanelPosition(event.getX()); 748 } 749 super.onTouchEvent(event); 750 return true; 751 } 752 handleQsTouch(MotionEvent event)753 private boolean handleQsTouch(MotionEvent event) { 754 final int action = event.getActionMasked(); 755 if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f 756 && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded 757 && mQsExpansionEnabled) { 758 759 // Down in the empty area while fully expanded - go to QS. 760 mQsTracking = true; 761 mConflictingQsExpansionGesture = true; 762 onQsExpansionStarted(); 763 mInitialHeightOnTouch = mQsExpansionHeight; 764 mInitialTouchY = event.getX(); 765 mInitialTouchX = event.getY(); 766 } 767 if (!isFullyCollapsed()) { 768 handleQsDown(event); 769 } 770 if (!mQsExpandImmediate && mQsTracking) { 771 onQsTouch(event); 772 if (!mConflictingQsExpansionGesture) { 773 return true; 774 } 775 } 776 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 777 mConflictingQsExpansionGesture = false; 778 } 779 if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed() 780 && mQsExpansionEnabled) { 781 mTwoFingerQsExpandPossible = true; 782 } 783 if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) 784 && event.getY(event.getActionIndex()) < mStatusBarMinHeight) { 785 MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_QS, 1); 786 mQsExpandImmediate = true; 787 requestPanelHeightUpdate(); 788 789 // Normally, we start listening when the panel is expanded, but here we need to start 790 // earlier so the state is already up to date when dragging down. 791 setListening(true); 792 } 793 return false; 794 } 795 isInQsArea(float x, float y)796 private boolean isInQsArea(float x, float y) { 797 return (x >= mScrollView.getX() && x <= mScrollView.getX() + mScrollView.getWidth()) && 798 (y <= mNotificationStackScroller.getBottomMostNotificationBottom() 799 || y <= mQsContainer.getY() + mQsContainer.getHeight()); 800 } 801 isOpenQsEvent(MotionEvent event)802 private boolean isOpenQsEvent(MotionEvent event) { 803 final int pointerCount = event.getPointerCount(); 804 final int action = event.getActionMasked(); 805 806 final boolean twoFingerDrag = action == MotionEvent.ACTION_POINTER_DOWN 807 && pointerCount == 2; 808 809 final boolean stylusButtonClickDrag = action == MotionEvent.ACTION_DOWN 810 && (event.isButtonPressed(MotionEvent.BUTTON_STYLUS_PRIMARY) 811 || event.isButtonPressed(MotionEvent.BUTTON_STYLUS_SECONDARY)); 812 813 final boolean mouseButtonClickDrag = action == MotionEvent.ACTION_DOWN 814 && (event.isButtonPressed(MotionEvent.BUTTON_SECONDARY) 815 || event.isButtonPressed(MotionEvent.BUTTON_TERTIARY)); 816 817 return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag; 818 } 819 handleQsDown(MotionEvent event)820 private void handleQsDown(MotionEvent event) { 821 if (event.getActionMasked() == MotionEvent.ACTION_DOWN 822 && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) { 823 mQsTracking = true; 824 onQsExpansionStarted(); 825 mInitialHeightOnTouch = mQsExpansionHeight; 826 mInitialTouchY = event.getX(); 827 mInitialTouchX = event.getY(); 828 829 // If we interrupt an expansion gesture here, make sure to update the state correctly. 830 notifyExpandingFinished(); 831 } 832 } 833 834 @Override flingExpands(float vel, float vectorVel, float x, float y)835 protected boolean flingExpands(float vel, float vectorVel, float x, float y) { 836 boolean expands = super.flingExpands(vel, vectorVel, x, y); 837 838 // If we are already running a QS expansion, make sure that we keep the panel open. 839 if (mQsExpansionAnimator != null) { 840 expands = true; 841 } 842 return expands; 843 } 844 845 @Override hasConflictingGestures()846 protected boolean hasConflictingGestures() { 847 return mStatusBar.getBarState() != StatusBarState.SHADE; 848 } 849 850 @Override shouldGestureIgnoreXTouchSlop(float x, float y)851 protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) { 852 return !mAfforanceHelper.isOnAffordanceIcon(x, y); 853 } 854 onQsTouch(MotionEvent event)855 private void onQsTouch(MotionEvent event) { 856 int pointerIndex = event.findPointerIndex(mTrackingPointer); 857 if (pointerIndex < 0) { 858 pointerIndex = 0; 859 mTrackingPointer = event.getPointerId(pointerIndex); 860 } 861 final float y = event.getY(pointerIndex); 862 final float x = event.getX(pointerIndex); 863 final float h = y - mInitialTouchY; 864 865 switch (event.getActionMasked()) { 866 case MotionEvent.ACTION_DOWN: 867 mQsTracking = true; 868 mInitialTouchY = y; 869 mInitialTouchX = x; 870 onQsExpansionStarted(); 871 mInitialHeightOnTouch = mQsExpansionHeight; 872 initVelocityTracker(); 873 trackMovement(event); 874 break; 875 876 case MotionEvent.ACTION_POINTER_UP: 877 final int upPointer = event.getPointerId(event.getActionIndex()); 878 if (mTrackingPointer == upPointer) { 879 // gesture is ongoing, find a new pointer to track 880 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 881 final float newY = event.getY(newIndex); 882 final float newX = event.getX(newIndex); 883 mTrackingPointer = event.getPointerId(newIndex); 884 mInitialHeightOnTouch = mQsExpansionHeight; 885 mInitialTouchY = newY; 886 mInitialTouchX = newX; 887 } 888 break; 889 890 case MotionEvent.ACTION_MOVE: 891 setQsExpansion(h + mInitialHeightOnTouch); 892 if (h >= getFalsingThreshold()) { 893 mQsTouchAboveFalsingThreshold = true; 894 } 895 trackMovement(event); 896 break; 897 898 case MotionEvent.ACTION_UP: 899 case MotionEvent.ACTION_CANCEL: 900 mQsTracking = false; 901 mTrackingPointer = -1; 902 trackMovement(event); 903 float fraction = getQsExpansionFraction(); 904 if ((fraction != 0f || y >= mInitialTouchY) 905 && (fraction != 1f || y <= mInitialTouchY)) { 906 flingQsWithCurrentVelocity(y, 907 event.getActionMasked() == MotionEvent.ACTION_CANCEL); 908 } else { 909 logQsSwipeDown(y); 910 mScrollYOverride = -1; 911 } 912 if (mVelocityTracker != null) { 913 mVelocityTracker.recycle(); 914 mVelocityTracker = null; 915 } 916 break; 917 } 918 } 919 getFalsingThreshold()920 private int getFalsingThreshold() { 921 float factor = mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f; 922 return (int) (mQsFalsingThreshold * factor); 923 } 924 925 @Override onOverscrolled(float lastTouchX, float lastTouchY, int amount)926 public void onOverscrolled(float lastTouchX, float lastTouchY, int amount) { 927 if (mIntercepting && shouldQuickSettingsIntercept(lastTouchX, lastTouchY, 928 -1 /* yDiff: Not relevant here */)) { 929 mQsTracking = true; 930 onQsExpansionStarted(amount); 931 mInitialHeightOnTouch = mQsExpansionHeight; 932 mInitialTouchY = mLastTouchY; 933 mInitialTouchX = mLastTouchX; 934 } 935 } 936 937 @Override onOverscrollTopChanged(float amount, boolean isRubberbanded)938 public void onOverscrollTopChanged(float amount, boolean isRubberbanded) { 939 cancelQsAnimation(); 940 if (!mQsExpansionEnabled) { 941 amount = 0f; 942 } 943 float rounded = amount >= 1f ? amount : 0f; 944 mStackScrollerOverscrolling = rounded != 0f && isRubberbanded; 945 mQsExpansionFromOverscroll = rounded != 0f; 946 mLastOverscroll = rounded; 947 updateQsState(); 948 setQsExpansion(mQsMinExpansionHeight + rounded); 949 } 950 951 @Override flingTopOverscroll(float velocity, boolean open)952 public void flingTopOverscroll(float velocity, boolean open) { 953 mLastOverscroll = 0f; 954 setQsExpansion(mQsExpansionHeight); 955 flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled, 956 new Runnable() { 957 @Override 958 public void run() { 959 mStackScrollerOverscrolling = false; 960 mQsExpansionFromOverscroll = false; 961 updateQsState(); 962 } 963 }, false /* isClick */); 964 } 965 onQsExpansionStarted()966 private void onQsExpansionStarted() { 967 onQsExpansionStarted(0); 968 } 969 onQsExpansionStarted(int overscrollAmount)970 private void onQsExpansionStarted(int overscrollAmount) { 971 cancelQsAnimation(); 972 cancelHeightAnimator(); 973 974 // Reset scroll position and apply that position to the expanded height. 975 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount; 976 if (mScrollView.getScrollY() != 0) { 977 mScrollYOverride = mScrollView.getScrollY(); 978 } 979 mScrollView.scrollTo(0, 0); 980 setQsExpansion(height); 981 requestPanelHeightUpdate(); 982 } 983 setQsExpanded(boolean expanded)984 private void setQsExpanded(boolean expanded) { 985 boolean changed = mQsExpanded != expanded; 986 if (changed) { 987 mQsExpanded = expanded; 988 updateQsState(); 989 requestPanelHeightUpdate(); 990 mNotificationStackScroller.setInterceptDelegateEnabled(expanded); 991 mStatusBar.setQsExpanded(expanded); 992 mQsPanel.setExpanded(expanded); 993 mNotificationContainerParent.setQsExpanded(expanded); 994 } 995 } 996 setBarState(int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade)997 public void setBarState(int statusBarState, boolean keyguardFadingAway, 998 boolean goingToFullShade) { 999 int oldState = mStatusBarState; 1000 boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD; 1001 setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade); 1002 setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade); 1003 1004 mStatusBarState = statusBarState; 1005 mKeyguardShowing = keyguardShowing; 1006 1007 if (goingToFullShade || (oldState == StatusBarState.KEYGUARD 1008 && statusBarState == StatusBarState.SHADE_LOCKED)) { 1009 animateKeyguardStatusBarOut(); 1010 animateHeaderSlidingIn(); 1011 } else if (oldState == StatusBarState.SHADE_LOCKED 1012 && statusBarState == StatusBarState.KEYGUARD) { 1013 animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD); 1014 animateHeaderSlidingOut(); 1015 } else { 1016 mKeyguardStatusBar.setAlpha(1f); 1017 mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE); 1018 if (keyguardShowing && oldState != mStatusBarState) { 1019 mKeyguardBottomArea.updateLeftAffordance(); 1020 mAfforanceHelper.updatePreviews(); 1021 } 1022 } 1023 if (keyguardShowing) { 1024 updateDozingVisibilities(false /* animate */); 1025 } 1026 resetVerticalPanelPosition(); 1027 updateQsState(); 1028 } 1029 1030 private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() { 1031 @Override 1032 public void run() { 1033 mKeyguardStatusViewAnimating = false; 1034 mKeyguardStatusView.setVisibility(View.GONE); 1035 } 1036 }; 1037 1038 private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() { 1039 @Override 1040 public void run() { 1041 mKeyguardStatusViewAnimating = false; 1042 } 1043 }; 1044 1045 private final Animator.AnimatorListener mAnimateHeaderSlidingInListener 1046 = new AnimatorListenerAdapter() { 1047 @Override 1048 public void onAnimationEnd(Animator animation) { 1049 mHeaderAnimating = false; 1050 mQsContainerAnimator = null; 1051 mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater); 1052 } 1053 }; 1054 1055 private final OnLayoutChangeListener mQsContainerAnimatorUpdater 1056 = new OnLayoutChangeListener() { 1057 @Override 1058 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 1059 int oldTop, int oldRight, int oldBottom) { 1060 int oldHeight = oldBottom - oldTop; 1061 int height = bottom - top; 1062 if (height != oldHeight && mQsContainerAnimator != null) { 1063 PropertyValuesHolder[] values = mQsContainerAnimator.getValues(); 1064 float newEndValue = mHeader.getCollapsedHeight() + mQsPeekHeight - height - top; 1065 float newStartValue = -height - top; 1066 values[0].setFloatValues(newStartValue, newEndValue); 1067 mQsContainerAnimator.setCurrentPlayTime(mQsContainerAnimator.getCurrentPlayTime()); 1068 } 1069 } 1070 }; 1071 1072 private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn 1073 = new ViewTreeObserver.OnPreDrawListener() { 1074 @Override 1075 public boolean onPreDraw() { 1076 getViewTreeObserver().removeOnPreDrawListener(this); 1077 long delay = mStatusBarState == StatusBarState.SHADE_LOCKED 1078 ? 0 1079 : mStatusBar.calculateGoingToFullShadeDelay(); 1080 mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight); 1081 mHeader.animate() 1082 .translationY(0f) 1083 .setStartDelay(delay) 1084 .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE) 1085 .setInterpolator(mFastOutSlowInInterpolator) 1086 .start(); 1087 mQsContainer.setY(-mQsContainer.getHeight()); 1088 mQsContainerAnimator = ObjectAnimator.ofFloat(mQsContainer, View.TRANSLATION_Y, 1089 mQsContainer.getTranslationY(), 1090 mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight() 1091 - mQsContainer.getTop()); 1092 mQsContainerAnimator.setStartDelay(delay); 1093 mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE); 1094 mQsContainerAnimator.setInterpolator(mFastOutSlowInInterpolator); 1095 mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener); 1096 mQsContainerAnimator.start(); 1097 mQsContainer.addOnLayoutChangeListener(mQsContainerAnimatorUpdater); 1098 return true; 1099 } 1100 }; 1101 animateHeaderSlidingIn()1102 private void animateHeaderSlidingIn() { 1103 // If the QS is already expanded we don't need to slide in the header as it's already 1104 // visible. 1105 if (!mQsExpanded) { 1106 mHeaderAnimating = true; 1107 getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn); 1108 } 1109 } 1110 animateHeaderSlidingOut()1111 private void animateHeaderSlidingOut() { 1112 mHeaderAnimating = true; 1113 mHeader.animate().y(-mHeader.getHeight()) 1114 .setStartDelay(0) 1115 .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD) 1116 .setInterpolator(mFastOutSlowInInterpolator) 1117 .setListener(new AnimatorListenerAdapter() { 1118 @Override 1119 public void onAnimationEnd(Animator animation) { 1120 mHeader.animate().setListener(null); 1121 mHeaderAnimating = false; 1122 updateQsState(); 1123 } 1124 }) 1125 .start(); 1126 mQsContainer.animate() 1127 .y(-mQsContainer.getHeight()) 1128 .setStartDelay(0) 1129 .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD) 1130 .setInterpolator(mFastOutSlowInInterpolator) 1131 .start(); 1132 } 1133 1134 private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() { 1135 @Override 1136 public void run() { 1137 mKeyguardStatusBar.setVisibility(View.INVISIBLE); 1138 mKeyguardStatusBar.setAlpha(1f); 1139 mKeyguardStatusBarAnimateAlpha = 1f; 1140 } 1141 }; 1142 animateKeyguardStatusBarOut()1143 private void animateKeyguardStatusBarOut() { 1144 ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f); 1145 anim.addUpdateListener(mStatusBarAnimateAlphaListener); 1146 anim.setStartDelay(mStatusBar.isKeyguardFadingAway() 1147 ? mStatusBar.getKeyguardFadingAwayDelay() 1148 : 0); 1149 anim.setDuration(mStatusBar.isKeyguardFadingAway() 1150 ? mStatusBar.getKeyguardFadingAwayDuration() / 2 1151 : StackStateAnimator.ANIMATION_DURATION_STANDARD); 1152 anim.setInterpolator(mDozeAnimationInterpolator); 1153 anim.addListener(new AnimatorListenerAdapter() { 1154 @Override 1155 public void onAnimationEnd(Animator animation) { 1156 mAnimateKeyguardStatusBarInvisibleEndRunnable.run(); 1157 } 1158 }); 1159 anim.start(); 1160 } 1161 1162 private final ValueAnimator.AnimatorUpdateListener mStatusBarAnimateAlphaListener = 1163 new ValueAnimator.AnimatorUpdateListener() { 1164 @Override 1165 public void onAnimationUpdate(ValueAnimator animation) { 1166 mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue(); 1167 updateHeaderKeyguardAlpha(); 1168 } 1169 }; 1170 animateKeyguardStatusBarIn(long duration)1171 private void animateKeyguardStatusBarIn(long duration) { 1172 mKeyguardStatusBar.setVisibility(View.VISIBLE); 1173 mKeyguardStatusBar.setAlpha(0f); 1174 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); 1175 anim.addUpdateListener(mStatusBarAnimateAlphaListener); 1176 anim.setDuration(duration); 1177 anim.setInterpolator(mDozeAnimationInterpolator); 1178 anim.start(); 1179 } 1180 1181 private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() { 1182 @Override 1183 public void run() { 1184 mKeyguardBottomArea.setVisibility(View.GONE); 1185 } 1186 }; 1187 setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade)1188 private void setKeyguardBottomAreaVisibility(int statusBarState, 1189 boolean goingToFullShade) { 1190 if (goingToFullShade) { 1191 mKeyguardBottomArea.animate().cancel(); 1192 mKeyguardBottomArea.animate() 1193 .alpha(0f) 1194 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) 1195 .setDuration(mStatusBar.getKeyguardFadingAwayDuration() / 2) 1196 .setInterpolator(PhoneStatusBar.ALPHA_OUT) 1197 .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable) 1198 .start(); 1199 } else if (statusBarState == StatusBarState.KEYGUARD 1200 || statusBarState == StatusBarState.SHADE_LOCKED) { 1201 mKeyguardBottomArea.animate().cancel(); 1202 if (!mDozing) { 1203 mKeyguardBottomArea.setVisibility(View.VISIBLE); 1204 } 1205 mKeyguardBottomArea.setAlpha(1f); 1206 } else { 1207 mKeyguardBottomArea.animate().cancel(); 1208 mKeyguardBottomArea.setVisibility(View.GONE); 1209 mKeyguardBottomArea.setAlpha(1f); 1210 } 1211 } 1212 setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade)1213 private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway, 1214 boolean goingToFullShade) { 1215 if ((!keyguardFadingAway && mStatusBarState == StatusBarState.KEYGUARD 1216 && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) { 1217 mKeyguardStatusView.animate().cancel(); 1218 mKeyguardStatusViewAnimating = true; 1219 mKeyguardStatusView.animate() 1220 .alpha(0f) 1221 .setStartDelay(0) 1222 .setDuration(160) 1223 .setInterpolator(PhoneStatusBar.ALPHA_OUT) 1224 .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable); 1225 if (keyguardFadingAway) { 1226 mKeyguardStatusView.animate() 1227 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) 1228 .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) 1229 .start(); 1230 } 1231 } else if (mStatusBarState == StatusBarState.SHADE_LOCKED 1232 && statusBarState == StatusBarState.KEYGUARD) { 1233 mKeyguardStatusView.animate().cancel(); 1234 mKeyguardStatusView.setVisibility(View.VISIBLE); 1235 mKeyguardStatusViewAnimating = true; 1236 mKeyguardStatusView.setAlpha(0f); 1237 mKeyguardStatusView.animate() 1238 .alpha(1f) 1239 .setStartDelay(0) 1240 .setDuration(320) 1241 .setInterpolator(PhoneStatusBar.ALPHA_IN) 1242 .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable); 1243 } else if (statusBarState == StatusBarState.KEYGUARD) { 1244 mKeyguardStatusView.animate().cancel(); 1245 mKeyguardStatusViewAnimating = false; 1246 mKeyguardStatusView.setVisibility(View.VISIBLE); 1247 mKeyguardStatusView.setAlpha(1f); 1248 } else { 1249 mKeyguardStatusView.animate().cancel(); 1250 mKeyguardStatusViewAnimating = false; 1251 mKeyguardStatusView.setVisibility(View.GONE); 1252 mKeyguardStatusView.setAlpha(1f); 1253 } 1254 } 1255 updateQsState()1256 private void updateQsState() { 1257 boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating; 1258 mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating) 1259 ? View.VISIBLE 1260 : View.INVISIBLE); 1261 mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating) 1262 || (mQsExpanded && !mStackScrollerOverscrolling)); 1263 mNotificationStackScroller.setScrollingEnabled( 1264 mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded 1265 || mQsExpansionFromOverscroll)); 1266 mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE); 1267 mQsContainer.setVisibility( 1268 mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE); 1269 mScrollView.setTouchEnabled(mQsExpanded); 1270 updateEmptyShadeView(); 1271 mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded 1272 && !mStackScrollerOverscrolling && mQsScrimEnabled 1273 ? View.VISIBLE 1274 : View.INVISIBLE); 1275 if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) { 1276 mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */); 1277 } 1278 } 1279 setQsExpansion(float height)1280 private void setQsExpansion(float height) { 1281 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); 1282 mQsFullyExpanded = height == mQsMaxExpansionHeight; 1283 if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) { 1284 setQsExpanded(true); 1285 } else if (height <= mQsMinExpansionHeight && mQsExpanded) { 1286 setQsExpanded(false); 1287 if (mLastAnnouncementWasQuickSettings && !mTracking && !isCollapsing()) { 1288 announceForAccessibility(getKeyguardOrLockScreenString()); 1289 mLastAnnouncementWasQuickSettings = false; 1290 } 1291 } 1292 mQsExpansionHeight = height; 1293 mHeader.setExpansion(getHeaderExpansionFraction()); 1294 setQsTranslation(height); 1295 requestScrollerTopPaddingUpdate(false /* animate */); 1296 updateNotificationScrim(height); 1297 if (mKeyguardShowing) { 1298 updateHeaderKeyguard(); 1299 } 1300 if (mStatusBarState == StatusBarState.SHADE_LOCKED 1301 || mStatusBarState == StatusBarState.KEYGUARD) { 1302 updateKeyguardBottomAreaAlpha(); 1303 } 1304 if (mStatusBarState == StatusBarState.SHADE && mQsExpanded 1305 && !mStackScrollerOverscrolling && mQsScrimEnabled) { 1306 mQsNavbarScrim.setAlpha(getQsExpansionFraction()); 1307 } 1308 1309 // Upon initialisation when we are not layouted yet we don't want to announce that we are 1310 // fully expanded, hence the != 0.0f check. 1311 if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) { 1312 announceForAccessibility(getContext().getString( 1313 R.string.accessibility_desc_quick_settings)); 1314 mLastAnnouncementWasQuickSettings = true; 1315 } 1316 if (DEBUG) { 1317 invalidate(); 1318 } 1319 } 1320 getKeyguardOrLockScreenString()1321 private String getKeyguardOrLockScreenString() { 1322 if (mStatusBarState == StatusBarState.KEYGUARD) { 1323 return getContext().getString(R.string.accessibility_desc_lock_screen); 1324 } else { 1325 return getContext().getString(R.string.accessibility_desc_notification_shade); 1326 } 1327 } 1328 updateNotificationScrim(float height)1329 private void updateNotificationScrim(float height) { 1330 int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance; 1331 float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance); 1332 progress = Math.max(0.0f, Math.min(progress, 1.0f)); 1333 } 1334 getHeaderExpansionFraction()1335 private float getHeaderExpansionFraction() { 1336 if (!mKeyguardShowing) { 1337 return getQsExpansionFraction(); 1338 } else { 1339 return 1f; 1340 } 1341 } 1342 setQsTranslation(float height)1343 private void setQsTranslation(float height) { 1344 if (!mHeaderAnimating) { 1345 mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation()); 1346 } 1347 if (mKeyguardShowing && !mHeaderAnimating) { 1348 mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0)); 1349 } 1350 } 1351 calculateQsTopPadding()1352 private float calculateQsTopPadding() { 1353 if (mKeyguardShowing 1354 && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) { 1355 1356 // Either QS pushes the notifications down when fully expanded, or QS is fully above the 1357 // notifications (mostly on tablets). maxNotifications denotes the normal top padding 1358 // on Keyguard, maxQs denotes the top padding from the quick settings panel. We need to 1359 // take the maximum and linearly interpolate with the panel expansion for a nice motion. 1360 int maxNotifications = mClockPositionResult.stackScrollerPadding 1361 - mClockPositionResult.stackScrollerPaddingAdjustment 1362 - mNotificationTopPadding; 1363 int maxQs = getTempQsMaxExpansion(); 1364 int max = mStatusBarState == StatusBarState.KEYGUARD 1365 ? Math.max(maxNotifications, maxQs) 1366 : maxQs; 1367 return (int) interpolate(getExpandedFraction(), 1368 mQsMinExpansionHeight, max); 1369 } else if (mQsSizeChangeAnimator != null) { 1370 return (int) mQsSizeChangeAnimator.getAnimatedValue(); 1371 } else if (mKeyguardShowing && mScrollYOverride == -1) { 1372 1373 // We can only do the smoother transition on Keyguard when we also are not collapsing 1374 // from a scrolled quick settings. 1375 return interpolate(getQsExpansionFraction(), 1376 mNotificationStackScroller.getIntrinsicPadding() - mNotificationTopPadding, 1377 mQsMaxExpansionHeight); 1378 } else { 1379 return mQsExpansionHeight; 1380 } 1381 } 1382 requestScrollerTopPaddingUpdate(boolean animate)1383 private void requestScrollerTopPaddingUpdate(boolean animate) { 1384 mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), 1385 mScrollView.getScrollY(), 1386 mAnimateNextTopPaddingChange || animate, 1387 mKeyguardShowing 1388 && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)); 1389 mAnimateNextTopPaddingChange = false; 1390 } 1391 trackMovement(MotionEvent event)1392 private void trackMovement(MotionEvent event) { 1393 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 1394 mLastTouchX = event.getX(); 1395 mLastTouchY = event.getY(); 1396 } 1397 initVelocityTracker()1398 private void initVelocityTracker() { 1399 if (mVelocityTracker != null) { 1400 mVelocityTracker.recycle(); 1401 } 1402 mVelocityTracker = VelocityTracker.obtain(); 1403 } 1404 getCurrentVelocity()1405 private float getCurrentVelocity() { 1406 if (mVelocityTracker == null) { 1407 return 0; 1408 } 1409 mVelocityTracker.computeCurrentVelocity(1000); 1410 return mVelocityTracker.getYVelocity(); 1411 } 1412 cancelQsAnimation()1413 private void cancelQsAnimation() { 1414 if (mQsExpansionAnimator != null) { 1415 mQsExpansionAnimator.cancel(); 1416 } 1417 } 1418 flingSettings(float vel, boolean expand)1419 private void flingSettings(float vel, boolean expand) { 1420 flingSettings(vel, expand, null, false /* isClick */); 1421 } 1422 flingSettings(float vel, boolean expand, final Runnable onFinishRunnable, boolean isClick)1423 private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable, 1424 boolean isClick) { 1425 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight; 1426 if (target == mQsExpansionHeight) { 1427 mScrollYOverride = -1; 1428 if (onFinishRunnable != null) { 1429 onFinishRunnable.run(); 1430 } 1431 return; 1432 } 1433 boolean belowFalsingThreshold = isBelowFalsingThreshold(); 1434 if (belowFalsingThreshold) { 1435 vel = 0; 1436 } 1437 mScrollView.setBlockFlinging(true); 1438 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target); 1439 if (isClick) { 1440 animator.setInterpolator(mTouchResponseInterpolator); 1441 animator.setDuration(368); 1442 } else { 1443 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel); 1444 } 1445 if (belowFalsingThreshold) { 1446 animator.setDuration(350); 1447 } 1448 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 1449 @Override 1450 public void onAnimationUpdate(ValueAnimator animation) { 1451 setQsExpansion((Float) animation.getAnimatedValue()); 1452 } 1453 }); 1454 animator.addListener(new AnimatorListenerAdapter() { 1455 @Override 1456 public void onAnimationEnd(Animator animation) { 1457 mScrollView.setBlockFlinging(false); 1458 mScrollYOverride = -1; 1459 mQsExpansionAnimator = null; 1460 if (onFinishRunnable != null) { 1461 onFinishRunnable.run(); 1462 } 1463 } 1464 }); 1465 animator.start(); 1466 mQsExpansionAnimator = animator; 1467 mQsAnimatorExpand = expand; 1468 } 1469 1470 /** 1471 * @return Whether we should intercept a gesture to open Quick Settings. 1472 */ shouldQuickSettingsIntercept(float x, float y, float yDiff)1473 private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { 1474 if (!mQsExpansionEnabled || mCollapsedOnDown) { 1475 return false; 1476 } 1477 View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader; 1478 boolean onHeader = x >= header.getX() && x <= header.getX() + header.getWidth() 1479 && y >= header.getTop() && y <= header.getBottom(); 1480 if (mQsExpanded) { 1481 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y); 1482 } else { 1483 return onHeader; 1484 } 1485 } 1486 1487 @Override isScrolledToBottom()1488 protected boolean isScrolledToBottom() { 1489 if (!isInSettings()) { 1490 return mStatusBar.getBarState() == StatusBarState.KEYGUARD 1491 || mNotificationStackScroller.isScrolledToBottom(); 1492 } else { 1493 return mScrollView.isScrolledToBottom(); 1494 } 1495 } 1496 1497 @Override getMaxPanelHeight()1498 protected int getMaxPanelHeight() { 1499 int min = mStatusBarMinHeight; 1500 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD 1501 && mNotificationStackScroller.getNotGoneChildCount() == 0) { 1502 int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount()) 1503 * HEADER_RUBBERBAND_FACTOR); 1504 min = Math.max(min, minHeight); 1505 } 1506 int maxHeight; 1507 if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) { 1508 maxHeight = calculatePanelHeightQsExpanded(); 1509 } else { 1510 maxHeight = calculatePanelHeightShade(); 1511 } 1512 maxHeight = Math.max(maxHeight, min); 1513 return maxHeight; 1514 } 1515 isInSettings()1516 private boolean isInSettings() { 1517 return mQsExpanded; 1518 } 1519 1520 @Override onHeightUpdated(float expandedHeight)1521 protected void onHeightUpdated(float expandedHeight) { 1522 if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) { 1523 positionClockAndNotifications(); 1524 } 1525 if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null 1526 && !mQsExpansionFromOverscroll) { 1527 float t; 1528 if (mKeyguardShowing) { 1529 1530 // On Keyguard, interpolate the QS expansion linearly to the panel expansion 1531 t = expandedHeight / getMaxPanelHeight(); 1532 } else { 1533 1534 // In Shade, interpolate linearly such that QS is closed whenever panel height is 1535 // minimum QS expansion + minStackHeight 1536 float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding() 1537 + mNotificationStackScroller.getMinStackHeight(); 1538 float panelHeightQsExpanded = calculatePanelHeightQsExpanded(); 1539 t = (expandedHeight - panelHeightQsCollapsed) 1540 / (panelHeightQsExpanded - panelHeightQsCollapsed); 1541 } 1542 setQsExpansion(mQsMinExpansionHeight 1543 + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight)); 1544 } 1545 updateStackHeight(expandedHeight); 1546 updateHeader(); 1547 updateUnlockIcon(); 1548 updateNotificationTranslucency(); 1549 updatePanelExpanded(); 1550 mNotificationStackScroller.setShadeExpanded(!isFullyCollapsed()); 1551 if (DEBUG) { 1552 invalidate(); 1553 } 1554 } 1555 updatePanelExpanded()1556 private void updatePanelExpanded() { 1557 boolean isExpanded = !isFullyCollapsed(); 1558 if (mPanelExpanded != isExpanded) { 1559 mHeadsUpManager.setIsExpanded(isExpanded); 1560 mStatusBar.setPanelExpanded(isExpanded); 1561 mPanelExpanded = isExpanded; 1562 } 1563 } 1564 1565 /** 1566 * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when 1567 * collapsing QS / the panel when QS was scrolled 1568 */ getTempQsMaxExpansion()1569 private int getTempQsMaxExpansion() { 1570 int qsTempMaxExpansion = mQsMaxExpansionHeight; 1571 if (mScrollYOverride != -1) { 1572 qsTempMaxExpansion -= mScrollYOverride; 1573 } 1574 return qsTempMaxExpansion; 1575 } 1576 calculatePanelHeightShade()1577 private int calculatePanelHeightShade() { 1578 int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin(); 1579 int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin 1580 - mTopPaddingAdjustment; 1581 maxHeight += mNotificationStackScroller.getTopPaddingOverflow(); 1582 return maxHeight; 1583 } 1584 calculatePanelHeightQsExpanded()1585 private int calculatePanelHeightQsExpanded() { 1586 float notificationHeight = mNotificationStackScroller.getHeight() 1587 - mNotificationStackScroller.getEmptyBottomMargin() 1588 - mNotificationStackScroller.getTopPadding(); 1589 1590 // When only empty shade view is visible in QS collapsed state, simulate that we would have 1591 // it in expanded QS state as well so we don't run into troubles when fading the view in/out 1592 // and expanding/collapsing the whole panel from/to quick settings. 1593 if (mNotificationStackScroller.getNotGoneChildCount() == 0 1594 && mShadeEmpty) { 1595 notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight() 1596 + mNotificationStackScroller.getBottomStackPeekSize() 1597 + mNotificationStackScroller.getCollapseSecondCardPadding(); 1598 } 1599 int maxQsHeight = mQsMaxExpansionHeight; 1600 1601 // If an animation is changing the size of the QS panel, take the animated value. 1602 if (mQsSizeChangeAnimator != null) { 1603 maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue(); 1604 } 1605 float totalHeight = Math.max( 1606 maxQsHeight + mNotificationStackScroller.getNotificationTopPadding(), 1607 mStatusBarState == StatusBarState.KEYGUARD 1608 ? mClockPositionResult.stackScrollerPadding - mTopPaddingAdjustment 1609 : 0) 1610 + notificationHeight; 1611 if (totalHeight > mNotificationStackScroller.getHeight()) { 1612 float fullyCollapsedHeight = maxQsHeight 1613 + mNotificationStackScroller.getMinStackHeight() 1614 + mNotificationStackScroller.getNotificationTopPadding() 1615 - getScrollViewScrollY(); 1616 totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight()); 1617 } 1618 return (int) totalHeight; 1619 } 1620 getScrollViewScrollY()1621 private int getScrollViewScrollY() { 1622 if (mScrollYOverride != -1 && !mQsTracking) { 1623 return mScrollYOverride; 1624 } else { 1625 return mScrollView.getScrollY(); 1626 } 1627 } updateNotificationTranslucency()1628 private void updateNotificationTranslucency() { 1629 float alpha = 1f; 1630 if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) { 1631 alpha = getFadeoutAlpha(); 1632 } 1633 mNotificationStackScroller.setAlpha(alpha); 1634 } 1635 getFadeoutAlpha()1636 private float getFadeoutAlpha() { 1637 float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight()) 1638 / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() 1639 - mNotificationStackScroller.getCollapseSecondCardPadding()); 1640 alpha = Math.max(0, Math.min(alpha, 1)); 1641 alpha = (float) Math.pow(alpha, 0.75); 1642 return alpha; 1643 } 1644 1645 @Override getOverExpansionAmount()1646 protected float getOverExpansionAmount() { 1647 return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */); 1648 } 1649 1650 @Override getOverExpansionPixels()1651 protected float getOverExpansionPixels() { 1652 return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */); 1653 } 1654 updateUnlockIcon()1655 private void updateUnlockIcon() { 1656 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1657 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1658 boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance; 1659 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1660 if (active && !mUnlockIconActive && mTracking) { 1661 lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null); 1662 lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150, 1663 mFastOutLinearInterpolator); 1664 } else if (!active && mUnlockIconActive && mTracking) { 1665 lockIcon.setImageAlpha(lockIcon.getRestingAlpha(), true /* animate */, 1666 150, mFastOutLinearInterpolator, null); 1667 lockIcon.setImageScale(1.0f, true, 150, 1668 mFastOutLinearInterpolator); 1669 } 1670 mUnlockIconActive = active; 1671 } 1672 } 1673 1674 /** 1675 * Hides the header when notifications are colliding with it. 1676 */ updateHeader()1677 private void updateHeader() { 1678 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1679 updateHeaderKeyguard(); 1680 } else { 1681 updateHeaderShade(); 1682 } 1683 1684 } 1685 updateHeaderShade()1686 private void updateHeaderShade() { 1687 if (!mHeaderAnimating) { 1688 mHeader.setTranslationY(getHeaderTranslation()); 1689 } 1690 setQsTranslation(mQsExpansionHeight); 1691 } 1692 getHeaderTranslation()1693 private float getHeaderTranslation() { 1694 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1695 return 0; 1696 } 1697 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1698 if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) { 1699 return 0; 1700 } else { 1701 return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight; 1702 } 1703 } 1704 float stackTranslation = mNotificationStackScroller.getStackTranslation(); 1705 float translation = stackTranslation / HEADER_RUBBERBAND_FACTOR; 1706 if (mHeadsUpManager.hasPinnedHeadsUp() || mIsExpansionFromHeadsUp) { 1707 translation = mNotificationStackScroller.getTopPadding() + stackTranslation 1708 - mNotificationTopPadding - mQsMinExpansionHeight; 1709 } 1710 return Math.min(0, translation); 1711 } 1712 1713 /** 1714 * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area) 1715 * during swiping up 1716 */ getKeyguardContentsAlpha()1717 private float getKeyguardContentsAlpha() { 1718 float alpha; 1719 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1720 1721 // When on Keyguard, we hide the header as soon as the top card of the notification 1722 // stack scroller is close enough (collision distance) to the bottom of the header. 1723 alpha = getNotificationsTopY() 1724 / 1725 (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance); 1726 } else { 1727 1728 // In SHADE_LOCKED, the top card is already really close to the header. Hide it as 1729 // soon as we start translating the stack. 1730 alpha = getNotificationsTopY() / mKeyguardStatusBar.getHeight(); 1731 } 1732 alpha = MathUtils.constrain(alpha, 0, 1); 1733 alpha = (float) Math.pow(alpha, 0.75); 1734 return alpha; 1735 } 1736 updateHeaderKeyguardAlpha()1737 private void updateHeaderKeyguardAlpha() { 1738 float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2); 1739 mKeyguardStatusBar.setAlpha(Math.min(getKeyguardContentsAlpha(), alphaQsExpansion) 1740 * mKeyguardStatusBarAnimateAlpha); 1741 mKeyguardStatusBar.setVisibility(mKeyguardStatusBar.getAlpha() != 0f 1742 && !mDozing ? VISIBLE : INVISIBLE); 1743 } 1744 updateHeaderKeyguard()1745 private void updateHeaderKeyguard() { 1746 updateHeaderKeyguardAlpha(); 1747 setQsTranslation(mQsExpansionHeight); 1748 } 1749 updateKeyguardBottomAreaAlpha()1750 private void updateKeyguardBottomAreaAlpha() { 1751 float alpha = Math.min(getKeyguardContentsAlpha(), 1 - getQsExpansionFraction()); 1752 mKeyguardBottomArea.setAlpha(alpha); 1753 mKeyguardBottomArea.setImportantForAccessibility(alpha == 0f 1754 ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS 1755 : IMPORTANT_FOR_ACCESSIBILITY_AUTO); 1756 } 1757 getNotificationsTopY()1758 private float getNotificationsTopY() { 1759 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1760 return getExpandedHeight(); 1761 } 1762 return mNotificationStackScroller.getNotificationsTopY(); 1763 } 1764 1765 @Override onExpandingStarted()1766 protected void onExpandingStarted() { 1767 super.onExpandingStarted(); 1768 mNotificationStackScroller.onExpansionStarted(); 1769 mIsExpanding = true; 1770 mQsExpandedWhenExpandingStarted = mQsFullyExpanded; 1771 if (mQsExpanded) { 1772 onQsExpansionStarted(); 1773 } 1774 } 1775 1776 @Override onExpandingFinished()1777 protected void onExpandingFinished() { 1778 super.onExpandingFinished(); 1779 mNotificationStackScroller.onExpansionStopped(); 1780 mHeadsUpManager.onExpandingFinished(); 1781 mIsExpanding = false; 1782 mScrollYOverride = -1; 1783 if (isFullyCollapsed()) { 1784 DejankUtils.postAfterTraversal(new Runnable() { 1785 @Override 1786 public void run() { 1787 setListening(false); 1788 } 1789 }); 1790 1791 // Workaround b/22639032: Make sure we invalidate something because else RenderThread 1792 // thinks we are actually drawing a frame put in reality we don't, so RT doesn't go 1793 // ahead with rendering and we jank. 1794 postOnAnimation(new Runnable() { 1795 @Override 1796 public void run() { 1797 getParent().invalidateChild(NotificationPanelView.this, mDummyDirtyRect); 1798 } 1799 }); 1800 } else { 1801 setListening(true); 1802 } 1803 mQsExpandImmediate = false; 1804 mTwoFingerQsExpandPossible = false; 1805 mIsExpansionFromHeadsUp = false; 1806 mNotificationStackScroller.setTrackingHeadsUp(false); 1807 mExpandingFromHeadsUp = false; 1808 setPanelScrimMinFraction(0.0f); 1809 } 1810 setListening(boolean listening)1811 private void setListening(boolean listening) { 1812 mHeader.setListening(listening); 1813 mKeyguardStatusBar.setListening(listening); 1814 mQsPanel.setListening(listening); 1815 } 1816 1817 @Override instantExpand()1818 public void instantExpand() { 1819 super.instantExpand(); 1820 setListening(true); 1821 } 1822 1823 @Override setOverExpansion(float overExpansion, boolean isPixels)1824 protected void setOverExpansion(float overExpansion, boolean isPixels) { 1825 if (mConflictingQsExpansionGesture || mQsExpandImmediate) { 1826 return; 1827 } 1828 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 1829 mNotificationStackScroller.setOnHeightChangedListener(null); 1830 if (isPixels) { 1831 mNotificationStackScroller.setOverScrolledPixels( 1832 overExpansion, true /* onTop */, false /* animate */); 1833 } else { 1834 mNotificationStackScroller.setOverScrollAmount( 1835 overExpansion, true /* onTop */, false /* animate */); 1836 } 1837 mNotificationStackScroller.setOnHeightChangedListener(this); 1838 } 1839 } 1840 1841 @Override onTrackingStarted()1842 protected void onTrackingStarted() { 1843 super.onTrackingStarted(); 1844 if (mQsFullyExpanded) { 1845 mQsExpandImmediate = true; 1846 } 1847 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1848 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1849 mAfforanceHelper.animateHideLeftRightIcon(); 1850 } 1851 mNotificationStackScroller.onPanelTrackingStarted(); 1852 } 1853 1854 @Override onTrackingStopped(boolean expand)1855 protected void onTrackingStopped(boolean expand) { 1856 super.onTrackingStopped(expand); 1857 if (expand) { 1858 mNotificationStackScroller.setOverScrolledPixels( 1859 0.0f, true /* onTop */, true /* animate */); 1860 } 1861 mNotificationStackScroller.onPanelTrackingStopped(); 1862 if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1863 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1864 if (!mHintAnimationRunning) { 1865 mAfforanceHelper.reset(true); 1866 } 1867 } 1868 if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1869 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1870 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1871 lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null); 1872 lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator); 1873 } 1874 } 1875 1876 @Override onHeightChanged(ExpandableView view, boolean needsAnimation)1877 public void onHeightChanged(ExpandableView view, boolean needsAnimation) { 1878 1879 // Block update if we are in quick settings and just the top padding changed 1880 // (i.e. view == null). 1881 if (view == null && mQsExpanded) { 1882 return; 1883 } 1884 requestPanelHeightUpdate(); 1885 } 1886 1887 @Override onReset(ExpandableView view)1888 public void onReset(ExpandableView view) { 1889 } 1890 1891 @Override onScrollChanged()1892 public void onScrollChanged() { 1893 if (mQsExpanded) { 1894 requestScrollerTopPaddingUpdate(false /* animate */); 1895 requestPanelHeightUpdate(); 1896 } 1897 } 1898 1899 @Override onConfigurationChanged(Configuration newConfig)1900 protected void onConfigurationChanged(Configuration newConfig) { 1901 super.onConfigurationChanged(newConfig); 1902 mAfforanceHelper.onConfigurationChanged(); 1903 if (newConfig.orientation != mLastOrientation) { 1904 resetVerticalPanelPosition(); 1905 } 1906 mLastOrientation = newConfig.orientation; 1907 } 1908 1909 @Override onApplyWindowInsets(WindowInsets insets)1910 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 1911 mNavigationBarBottomHeight = insets.getSystemWindowInsetBottom(); 1912 updateMaxHeadsUpTranslation(); 1913 return insets; 1914 } 1915 updateMaxHeadsUpTranslation()1916 private void updateMaxHeadsUpTranslation() { 1917 mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight); 1918 } 1919 1920 @Override onRtlPropertiesChanged(int layoutDirection)1921 public void onRtlPropertiesChanged(int layoutDirection) { 1922 if (layoutDirection != mOldLayoutDirection) { 1923 mAfforanceHelper.onRtlPropertiesChanged(); 1924 mOldLayoutDirection = layoutDirection; 1925 } 1926 } 1927 1928 @Override onClick(View v)1929 public void onClick(View v) { 1930 if (v == mHeader) { 1931 onQsExpansionStarted(); 1932 if (mQsExpanded) { 1933 flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */); 1934 } else if (mQsExpansionEnabled) { 1935 EventLogTags.writeSysuiLockscreenGesture( 1936 EventLogConstants.SYSUI_TAP_TO_OPEN_QS, 1937 0, 0); 1938 flingSettings(0 /* vel */, true /* expand */, null, true /* isClick */); 1939 } 1940 } 1941 } 1942 1943 @Override onAnimationToSideStarted(boolean rightPage, float translation, float vel)1944 public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) { 1945 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage; 1946 mIsLaunchTransitionRunning = true; 1947 mLaunchAnimationEndRunnable = null; 1948 float displayDensity = mStatusBar.getDisplayDensity(); 1949 int lengthDp = Math.abs((int) (translation / displayDensity)); 1950 int velocityDp = Math.abs((int) (vel / displayDensity)); 1951 if (start) { 1952 EventLogTags.writeSysuiLockscreenGesture( 1953 EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER, lengthDp, velocityDp); 1954 mKeyguardBottomArea.launchLeftAffordance(); 1955 } else { 1956 EventLogTags.writeSysuiLockscreenGesture( 1957 EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, lengthDp, velocityDp); 1958 mSecureCameraLaunchManager.startSecureCameraLaunch(); 1959 } 1960 mStatusBar.startLaunchTransitionTimeout(); 1961 mBlockTouches = true; 1962 } 1963 1964 @Override onAnimationToSideEnded()1965 public void onAnimationToSideEnded() { 1966 mIsLaunchTransitionRunning = false; 1967 mIsLaunchTransitionFinished = true; 1968 if (mLaunchAnimationEndRunnable != null) { 1969 mLaunchAnimationEndRunnable.run(); 1970 mLaunchAnimationEndRunnable = null; 1971 } 1972 } 1973 1974 @Override startUnlockHintAnimation()1975 protected void startUnlockHintAnimation() { 1976 super.startUnlockHintAnimation(); 1977 startHighlightIconAnimation(getCenterIcon()); 1978 } 1979 1980 /** 1981 * Starts the highlight (making it fully opaque) animation on an icon. 1982 */ startHighlightIconAnimation(final KeyguardAffordanceView icon)1983 private void startHighlightIconAnimation(final KeyguardAffordanceView icon) { 1984 icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1985 mFastOutSlowInInterpolator, new Runnable() { 1986 @Override 1987 public void run() { 1988 icon.setImageAlpha(icon.getRestingAlpha(), 1989 true /* animate */, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1990 mFastOutSlowInInterpolator, null); 1991 } 1992 }); 1993 } 1994 1995 @Override getMaxTranslationDistance()1996 public float getMaxTranslationDistance() { 1997 return (float) Math.hypot(getWidth(), getHeight()); 1998 } 1999 2000 @Override onSwipingStarted(boolean rightIcon)2001 public void onSwipingStarted(boolean rightIcon) { 2002 boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon 2003 : rightIcon; 2004 if (camera) { 2005 mSecureCameraLaunchManager.onSwipingStarted(); 2006 mKeyguardBottomArea.bindCameraPrewarmService(); 2007 } 2008 requestDisallowInterceptTouchEvent(true); 2009 mOnlyAffordanceInThisMotion = true; 2010 mQsTracking = false; 2011 } 2012 2013 @Override onSwipingAborted()2014 public void onSwipingAborted() { 2015 mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */); 2016 } 2017 2018 @Override onIconClicked(boolean rightIcon)2019 public void onIconClicked(boolean rightIcon) { 2020 if (mHintAnimationRunning) { 2021 return; 2022 } 2023 mHintAnimationRunning = true; 2024 mAfforanceHelper.startHintAnimation(rightIcon, new Runnable() { 2025 @Override 2026 public void run() { 2027 mHintAnimationRunning = false; 2028 mStatusBar.onHintFinished(); 2029 } 2030 }); 2031 rightIcon = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon : rightIcon; 2032 if (rightIcon) { 2033 mStatusBar.onCameraHintStarted(); 2034 } else { 2035 if (mKeyguardBottomArea.isLeftVoiceAssist()) { 2036 mStatusBar.onVoiceAssistHintStarted(); 2037 } else { 2038 mStatusBar.onPhoneHintStarted(); 2039 } 2040 } 2041 } 2042 2043 @Override getLeftIcon()2044 public KeyguardAffordanceView getLeftIcon() { 2045 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 2046 ? mKeyguardBottomArea.getRightView() 2047 : mKeyguardBottomArea.getLeftView(); 2048 } 2049 2050 @Override getCenterIcon()2051 public KeyguardAffordanceView getCenterIcon() { 2052 return mKeyguardBottomArea.getLockIcon(); 2053 } 2054 2055 @Override getRightIcon()2056 public KeyguardAffordanceView getRightIcon() { 2057 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 2058 ? mKeyguardBottomArea.getLeftView() 2059 : mKeyguardBottomArea.getRightView(); 2060 } 2061 2062 @Override getLeftPreview()2063 public View getLeftPreview() { 2064 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 2065 ? mKeyguardBottomArea.getRightPreview() 2066 : mKeyguardBottomArea.getLeftPreview(); 2067 } 2068 2069 @Override getRightPreview()2070 public View getRightPreview() { 2071 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 2072 ? mKeyguardBottomArea.getLeftPreview() 2073 : mKeyguardBottomArea.getRightPreview(); 2074 } 2075 2076 @Override getAffordanceFalsingFactor()2077 public float getAffordanceFalsingFactor() { 2078 return mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f; 2079 } 2080 2081 @Override getPeekHeight()2082 protected float getPeekHeight() { 2083 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 2084 return mNotificationStackScroller.getPeekHeight(); 2085 } else { 2086 return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR; 2087 } 2088 } 2089 2090 @Override getCannedFlingDurationFactor()2091 protected float getCannedFlingDurationFactor() { 2092 if (mQsExpanded) { 2093 return 0.7f; 2094 } else { 2095 return 0.6f; 2096 } 2097 } 2098 2099 @Override fullyExpandedClearAllVisible()2100 protected boolean fullyExpandedClearAllVisible() { 2101 return mNotificationStackScroller.isDismissViewNotGone() 2102 && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate; 2103 } 2104 2105 @Override isClearAllVisible()2106 protected boolean isClearAllVisible() { 2107 return mNotificationStackScroller.isDismissViewVisible(); 2108 } 2109 2110 @Override getClearAllHeight()2111 protected int getClearAllHeight() { 2112 return mNotificationStackScroller.getDismissViewHeight(); 2113 } 2114 2115 @Override isTrackingBlocked()2116 protected boolean isTrackingBlocked() { 2117 return mConflictingQsExpansionGesture && mQsExpanded; 2118 } 2119 notifyVisibleChildrenChanged()2120 public void notifyVisibleChildrenChanged() { 2121 if (mNotificationStackScroller.getNotGoneChildCount() != 0) { 2122 mReserveNotificationSpace.setVisibility(View.VISIBLE); 2123 } else { 2124 mReserveNotificationSpace.setVisibility(View.GONE); 2125 } 2126 } 2127 isQsExpanded()2128 public boolean isQsExpanded() { 2129 return mQsExpanded; 2130 } 2131 isQsDetailShowing()2132 public boolean isQsDetailShowing() { 2133 return mQsPanel.isShowingDetail(); 2134 } 2135 closeQsDetail()2136 public void closeQsDetail() { 2137 mQsPanel.closeDetail(); 2138 } 2139 2140 @Override shouldDelayChildPressedState()2141 public boolean shouldDelayChildPressedState() { 2142 return true; 2143 } 2144 isLaunchTransitionFinished()2145 public boolean isLaunchTransitionFinished() { 2146 return mIsLaunchTransitionFinished; 2147 } 2148 isLaunchTransitionRunning()2149 public boolean isLaunchTransitionRunning() { 2150 return mIsLaunchTransitionRunning; 2151 } 2152 setLaunchTransitionEndRunnable(Runnable r)2153 public void setLaunchTransitionEndRunnable(Runnable r) { 2154 mLaunchAnimationEndRunnable = r; 2155 } 2156 setEmptyDragAmount(float amount)2157 public void setEmptyDragAmount(float amount) { 2158 float factor = 0.8f; 2159 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 2160 factor = 0.4f; 2161 } else if (!mStatusBar.hasActiveNotifications()) { 2162 factor = 0.4f; 2163 } 2164 mEmptyDragAmount = amount * factor; 2165 positionClockAndNotifications(); 2166 } 2167 interpolate(float t, float start, float end)2168 private static float interpolate(float t, float start, float end) { 2169 return (1 - t) * start + t * end; 2170 } 2171 setDozing(boolean dozing, boolean animate)2172 public void setDozing(boolean dozing, boolean animate) { 2173 if (dozing == mDozing) return; 2174 mDozing = dozing; 2175 if (mStatusBarState == StatusBarState.KEYGUARD) { 2176 updateDozingVisibilities(animate); 2177 } 2178 } 2179 updateDozingVisibilities(boolean animate)2180 private void updateDozingVisibilities(boolean animate) { 2181 if (mDozing) { 2182 mKeyguardStatusBar.setVisibility(View.INVISIBLE); 2183 mKeyguardBottomArea.setVisibility(View.INVISIBLE); 2184 } else { 2185 mKeyguardBottomArea.setVisibility(View.VISIBLE); 2186 mKeyguardStatusBar.setVisibility(View.VISIBLE); 2187 if (animate) { 2188 animateKeyguardStatusBarIn(DOZE_ANIMATION_DURATION); 2189 mKeyguardBottomArea.startFinishDozeAnimation(); 2190 } 2191 } 2192 } 2193 2194 @Override isDozing()2195 public boolean isDozing() { 2196 return mDozing; 2197 } 2198 setShadeEmpty(boolean shadeEmpty)2199 public void setShadeEmpty(boolean shadeEmpty) { 2200 mShadeEmpty = shadeEmpty; 2201 updateEmptyShadeView(); 2202 } 2203 updateEmptyShadeView()2204 private void updateEmptyShadeView() { 2205 2206 // Hide "No notifications" in QS. 2207 mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded); 2208 } 2209 setQsScrimEnabled(boolean qsScrimEnabled)2210 public void setQsScrimEnabled(boolean qsScrimEnabled) { 2211 boolean changed = mQsScrimEnabled != qsScrimEnabled; 2212 mQsScrimEnabled = qsScrimEnabled; 2213 if (changed) { 2214 updateQsState(); 2215 } 2216 } 2217 setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher)2218 public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) { 2219 mKeyguardUserSwitcher = keyguardUserSwitcher; 2220 } 2221 2222 private final Runnable mUpdateHeader = new Runnable() { 2223 @Override 2224 public void run() { 2225 mHeader.updateEverything(); 2226 } 2227 }; 2228 onScreenTurningOn()2229 public void onScreenTurningOn() { 2230 mKeyguardStatusView.refreshTime(); 2231 } 2232 2233 @Override onEmptySpaceClicked(float x, float y)2234 public void onEmptySpaceClicked(float x, float y) { 2235 onEmptySpaceClick(x); 2236 } 2237 onMiddleClicked()2238 protected boolean onMiddleClicked() { 2239 switch (mStatusBar.getBarState()) { 2240 case StatusBarState.KEYGUARD: 2241 if (!mDozingOnDown) { 2242 EventLogTags.writeSysuiLockscreenGesture( 2243 EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT, 2244 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); 2245 startUnlockHintAnimation(); 2246 } 2247 return true; 2248 case StatusBarState.SHADE_LOCKED: 2249 if (!mQsExpanded) { 2250 mStatusBar.goToKeyguard(); 2251 } 2252 return true; 2253 case StatusBarState.SHADE: 2254 2255 // This gets called in the middle of the touch handling, where the state is still 2256 // that we are tracking the panel. Collapse the panel after this is done. 2257 post(mPostCollapseRunnable); 2258 return false; 2259 default: 2260 return true; 2261 } 2262 } 2263 2264 @Override dispatchDraw(Canvas canvas)2265 protected void dispatchDraw(Canvas canvas) { 2266 super.dispatchDraw(canvas); 2267 if (DEBUG) { 2268 Paint p = new Paint(); 2269 p.setColor(Color.RED); 2270 p.setStrokeWidth(2); 2271 p.setStyle(Paint.Style.STROKE); 2272 canvas.drawLine(0, getMaxPanelHeight(), getWidth(), getMaxPanelHeight(), p); 2273 p.setColor(Color.BLUE); 2274 canvas.drawLine(0, getExpandedHeight(), getWidth(), getExpandedHeight(), p); 2275 p.setColor(Color.GREEN); 2276 canvas.drawLine(0, calculatePanelHeightQsExpanded(), getWidth(), 2277 calculatePanelHeightQsExpanded(), p); 2278 p.setColor(Color.YELLOW); 2279 canvas.drawLine(0, calculatePanelHeightShade(), getWidth(), 2280 calculatePanelHeightShade(), p); 2281 p.setColor(Color.MAGENTA); 2282 canvas.drawLine(0, calculateQsTopPadding(), getWidth(), 2283 calculateQsTopPadding(), p); 2284 p.setColor(Color.CYAN); 2285 canvas.drawLine(0, mNotificationStackScroller.getTopPadding(), getWidth(), 2286 mNotificationStackScroller.getTopPadding(), p); 2287 } 2288 } 2289 2290 @Override onHeadsUpPinnedModeChanged(final boolean inPinnedMode)2291 public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) { 2292 if (inPinnedMode) { 2293 mHeadsUpExistenceChangedRunnable.run(); 2294 updateNotificationTranslucency(); 2295 } else { 2296 mHeadsUpAnimatingAway = true; 2297 mNotificationStackScroller.runAfterAnimationFinished( 2298 mHeadsUpExistenceChangedRunnable); 2299 } 2300 } 2301 2302 @Override onHeadsUpPinned(ExpandableNotificationRow headsUp)2303 public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { 2304 mNotificationStackScroller.generateHeadsUpAnimation(headsUp, true); 2305 } 2306 2307 @Override onHeadsUpUnPinned(ExpandableNotificationRow headsUp)2308 public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) { 2309 } 2310 2311 @Override onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp)2312 public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { 2313 mNotificationStackScroller.generateHeadsUpAnimation(entry.row, isHeadsUp); 2314 } 2315 2316 @Override setHeadsUpManager(HeadsUpManager headsUpManager)2317 public void setHeadsUpManager(HeadsUpManager headsUpManager) { 2318 super.setHeadsUpManager(headsUpManager); 2319 mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller, 2320 this); 2321 } 2322 setTrackingHeadsUp(boolean tracking)2323 public void setTrackingHeadsUp(boolean tracking) { 2324 if (tracking) { 2325 mNotificationStackScroller.setTrackingHeadsUp(true); 2326 mExpandingFromHeadsUp = true; 2327 } 2328 // otherwise we update the state when the expansion is finished 2329 } 2330 2331 @Override onClosingFinished()2332 protected void onClosingFinished() { 2333 super.onClosingFinished(); 2334 resetVerticalPanelPosition(); 2335 setClosingWithAlphaFadeout(false); 2336 } 2337 setClosingWithAlphaFadeout(boolean closing)2338 private void setClosingWithAlphaFadeout(boolean closing) { 2339 mClosingWithAlphaFadeOut = closing; 2340 mNotificationStackScroller.forceNoOverlappingRendering(closing); 2341 } 2342 2343 /** 2344 * Updates the vertical position of the panel so it is positioned closer to the touch 2345 * responsible for opening the panel. 2346 * 2347 * @param x the x-coordinate the touch event 2348 */ updateVerticalPanelPosition(float x)2349 private void updateVerticalPanelPosition(float x) { 2350 if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) { 2351 resetVerticalPanelPosition(); 2352 return; 2353 } 2354 float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2; 2355 float rightMost = getWidth() - mPositionMinSideMargin 2356 - mNotificationStackScroller.getWidth() / 2; 2357 if (Math.abs(x - getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) { 2358 x = getWidth() / 2; 2359 } 2360 x = Math.min(rightMost, Math.max(leftMost, x)); 2361 setVerticalPanelTranslation(x - 2362 (mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2)); 2363 } 2364 resetVerticalPanelPosition()2365 private void resetVerticalPanelPosition() { 2366 setVerticalPanelTranslation(0f); 2367 } 2368 setVerticalPanelTranslation(float translation)2369 private void setVerticalPanelTranslation(float translation) { 2370 mNotificationStackScroller.setTranslationX(translation); 2371 mScrollView.setTranslationX(translation); 2372 mHeader.setTranslationX(translation); 2373 } 2374 updateStackHeight(float stackHeight)2375 private void updateStackHeight(float stackHeight) { 2376 mNotificationStackScroller.setStackHeight(stackHeight); 2377 updateKeyguardBottomAreaAlpha(); 2378 } 2379 setPanelScrimMinFraction(float minFraction)2380 public void setPanelScrimMinFraction(float minFraction) { 2381 mBar.panelScrimMinFractionChanged(minFraction); 2382 } 2383 clearNotificattonEffects()2384 public void clearNotificattonEffects() { 2385 mStatusBar.clearNotificationEffects(); 2386 } 2387 isPanelVisibleBecauseOfHeadsUp()2388 protected boolean isPanelVisibleBecauseOfHeadsUp() { 2389 return mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway; 2390 } 2391 } 2392