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