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