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