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