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