1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.phone; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.PropertyValuesHolder; 23 import android.animation.ValueAnimator; 24 import android.content.Context; 25 import android.content.res.Configuration; 26 import android.graphics.Canvas; 27 import android.graphics.Color; 28 import android.graphics.Paint; 29 import android.util.AttributeSet; 30 import android.util.MathUtils; 31 import android.view.MotionEvent; 32 import android.view.VelocityTracker; 33 import android.view.View; 34 import android.view.ViewTreeObserver; 35 import android.view.accessibility.AccessibilityEvent; 36 import android.view.animation.AnimationUtils; 37 import android.view.animation.Interpolator; 38 import android.widget.FrameLayout; 39 import android.widget.TextView; 40 41 import com.android.keyguard.KeyguardStatusView; 42 import com.android.systemui.EventLogTags; 43 import com.android.systemui.EventLogConstants; 44 import com.android.systemui.R; 45 import com.android.systemui.qs.QSContainer; 46 import com.android.systemui.qs.QSPanel; 47 import com.android.systemui.statusbar.ExpandableView; 48 import com.android.systemui.statusbar.FlingAnimationUtils; 49 import com.android.systemui.statusbar.GestureRecorder; 50 import com.android.systemui.statusbar.KeyguardAffordanceView; 51 import com.android.systemui.statusbar.StatusBarState; 52 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; 53 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 54 import com.android.systemui.statusbar.stack.StackStateAnimator; 55 56 public class NotificationPanelView extends PanelView implements 57 ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, 58 View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener, 59 KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener { 60 61 private static final boolean DEBUG = false; 62 63 // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is 64 // changed. 65 private static final int CAP_HEIGHT = 1456; 66 private static final int FONT_HEIGHT = 2163; 67 68 private static final float HEADER_RUBBERBAND_FACTOR = 2.05f; 69 private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f; 70 71 public static final long DOZE_ANIMATION_DURATION = 700; 72 73 private KeyguardAffordanceHelper mAfforanceHelper; 74 private StatusBarHeaderView mHeader; 75 private KeyguardUserSwitcher mKeyguardUserSwitcher; 76 private KeyguardStatusBarView mKeyguardStatusBar; 77 private QSContainer mQsContainer; 78 private QSPanel mQsPanel; 79 private KeyguardStatusView mKeyguardStatusView; 80 private ObservableScrollView mScrollView; 81 private TextView mClockView; 82 private View mReserveNotificationSpace; 83 private View mQsNavbarScrim; 84 private View mNotificationContainerParent; 85 private NotificationStackScrollLayout mNotificationStackScroller; 86 private int mNotificationTopPadding; 87 private boolean mAnimateNextTopPaddingChange; 88 89 private int mTrackingPointer; 90 private VelocityTracker mVelocityTracker; 91 private boolean mQsTracking; 92 93 /** 94 * Handles launching the secure camera properly even when other applications may be using the 95 * camera hardware. 96 */ 97 private SecureCameraLaunchManager mSecureCameraLaunchManager; 98 99 /** 100 * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and 101 * the expansion for quick settings. 102 */ 103 private boolean mConflictingQsExpansionGesture; 104 105 /** 106 * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't 107 * intercepted yet. 108 */ 109 private boolean mIntercepting; 110 private boolean mQsExpanded; 111 private boolean mQsExpandedWhenExpandingStarted; 112 private boolean mQsFullyExpanded; 113 private boolean mKeyguardShowing; 114 private boolean mDozing; 115 private int mStatusBarState; 116 private float mInitialHeightOnTouch; 117 private float mInitialTouchX; 118 private float mInitialTouchY; 119 private float mLastTouchX; 120 private float mLastTouchY; 121 private float mQsExpansionHeight; 122 private int mQsMinExpansionHeight; 123 private int mQsMaxExpansionHeight; 124 private int mQsPeekHeight; 125 private boolean mStackScrollerOverscrolling; 126 private boolean mQsExpansionFromOverscroll; 127 private float mLastOverscroll; 128 private boolean mQsExpansionEnabled = true; 129 private ValueAnimator mQsExpansionAnimator; 130 private FlingAnimationUtils mFlingAnimationUtils; 131 private int mStatusBarMinHeight; 132 private boolean mUnlockIconActive; 133 private int mNotificationsHeaderCollideDistance; 134 private int mUnlockMoveDistance; 135 private float mEmptyDragAmount; 136 137 private Interpolator mFastOutSlowInInterpolator; 138 private Interpolator mFastOutLinearInterpolator; 139 private Interpolator mDozeAnimationInterpolator; 140 private ObjectAnimator mClockAnimator; 141 private int mClockAnimationTarget = -1; 142 private int mTopPaddingAdjustment; 143 private KeyguardClockPositionAlgorithm mClockPositionAlgorithm = 144 new KeyguardClockPositionAlgorithm(); 145 private KeyguardClockPositionAlgorithm.Result mClockPositionResult = 146 new KeyguardClockPositionAlgorithm.Result(); 147 private boolean mIsExpanding; 148 149 private boolean mBlockTouches; 150 private int mNotificationScrimWaitDistance; 151 // Used for two finger gesture as well as accessibility shortcut to QS. 152 private boolean mQsExpandImmediate; 153 private boolean mTwoFingerQsExpandPossible; 154 155 /** 156 * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still 157 * need to take this into account in our panel height calculation. 158 */ 159 private int mScrollYOverride = -1; 160 private boolean mQsAnimatorExpand; 161 private boolean mIsLaunchTransitionFinished; 162 private boolean mIsLaunchTransitionRunning; 163 private Runnable mLaunchAnimationEndRunnable; 164 private boolean mOnlyAffordanceInThisMotion; 165 private boolean mKeyguardStatusViewAnimating; 166 private boolean mHeaderAnimatingIn; 167 private ObjectAnimator mQsContainerAnimator; 168 private ValueAnimator mQsSizeChangeAnimator; 169 170 private boolean mShadeEmpty; 171 172 private boolean mQsScrimEnabled = true; 173 private boolean mLastAnnouncementWasQuickSettings; 174 private boolean mQsTouchAboveFalsingThreshold; 175 private int mQsFalsingThreshold; 176 177 private float mKeyguardStatusBarAnimateAlpha = 1f; 178 private int mOldLayoutDirection; 179 180 public NotificationPanelView(Context context, AttributeSet attrs) { 181 super(context, attrs); 182 setWillNotDraw(!DEBUG); 183 } 184 185 public void setStatusBar(PhoneStatusBar bar) { 186 mStatusBar = bar; 187 } 188 189 @Override 190 protected void onFinishInflate() { 191 super.onFinishInflate(); 192 mHeader = (StatusBarHeaderView) findViewById(R.id.header); 193 mHeader.setOnClickListener(this); 194 mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header); 195 mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view); 196 mQsContainer = (QSContainer) findViewById(R.id.quick_settings_container); 197 mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel); 198 mClockView = (TextView) findViewById(R.id.clock_view); 199 mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view); 200 mScrollView.setListener(this); 201 mScrollView.setFocusable(false); 202 mReserveNotificationSpace = findViewById(R.id.reserve_notification_space); 203 mNotificationContainerParent = findViewById(R.id.notification_container_parent); 204 mNotificationStackScroller = (NotificationStackScrollLayout) 205 findViewById(R.id.notification_stack_scroller); 206 mNotificationStackScroller.setOnHeightChangedListener(this); 207 mNotificationStackScroller.setOverscrollTopChangedListener(this); 208 mNotificationStackScroller.setOnEmptySpaceClickListener(this); 209 mNotificationStackScroller.setScrollView(mScrollView); 210 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), 211 android.R.interpolator.fast_out_slow_in); 212 mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(), 213 android.R.interpolator.fast_out_linear_in); 214 mDozeAnimationInterpolator = AnimationUtils.loadInterpolator(getContext(), 215 android.R.interpolator.linear_out_slow_in); 216 mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); 217 mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim); 218 mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext()); 219 mSecureCameraLaunchManager = 220 new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea); 221 222 // recompute internal state when qspanel height changes 223 mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() { 224 @Override 225 public void onLayoutChange(View v, int left, int top, int right, 226 int bottom, int oldLeft, int oldTop, int oldRight, 227 int oldBottom) { 228 final int height = bottom - top; 229 final int oldHeight = oldBottom - oldTop; 230 if (height != oldHeight) { 231 onScrollChanged(); 232 } 233 } 234 }); 235 } 236 237 @Override 238 protected void loadDimens() { 239 super.loadDimens(); 240 mNotificationTopPadding = getResources().getDimensionPixelSize( 241 R.dimen.notifications_top_padding); 242 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f); 243 mStatusBarMinHeight = getResources().getDimensionPixelSize( 244 com.android.internal.R.dimen.status_bar_height); 245 mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height); 246 mNotificationsHeaderCollideDistance = 247 getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance); 248 mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance); 249 mClockPositionAlgorithm.loadDimens(getResources()); 250 mNotificationScrimWaitDistance = 251 getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance); 252 mQsFalsingThreshold = getResources().getDimensionPixelSize( 253 R.dimen.qs_falsing_threshold); 254 } 255 256 public void updateResources() { 257 int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width); 258 int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity); 259 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mHeader.getLayoutParams(); 260 if (lp.width != panelWidth) { 261 lp.width = panelWidth; 262 lp.gravity = panelGravity; 263 mHeader.setLayoutParams(lp); 264 mHeader.post(mUpdateHeader); 265 } 266 267 lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams(); 268 if (lp.width != panelWidth) { 269 lp.width = panelWidth; 270 lp.gravity = panelGravity; 271 mNotificationStackScroller.setLayoutParams(lp); 272 } 273 274 lp = (FrameLayout.LayoutParams) mScrollView.getLayoutParams(); 275 if (lp.width != panelWidth) { 276 lp.width = panelWidth; 277 lp.gravity = panelGravity; 278 mScrollView.setLayoutParams(lp); 279 } 280 } 281 282 @Override 283 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 284 super.onLayout(changed, left, top, right, bottom); 285 286 // Update Clock Pivot 287 mKeyguardStatusView.setPivotX(getWidth() / 2); 288 mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize()); 289 290 // Calculate quick setting heights. 291 int oldMaxHeight = mQsMaxExpansionHeight; 292 mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight; 293 mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getDesiredHeight(); 294 positionClockAndNotifications(); 295 if (mQsExpanded && mQsFullyExpanded) { 296 mQsExpansionHeight = mQsMaxExpansionHeight; 297 requestScrollerTopPaddingUpdate(false /* animate */); 298 requestPanelHeightUpdate(); 299 300 // Size has changed, start an animation. 301 if (mQsMaxExpansionHeight != oldMaxHeight) { 302 startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight); 303 } 304 } else if (!mQsExpanded) { 305 setQsExpansion(mQsMinExpansionHeight + mLastOverscroll); 306 } 307 mNotificationStackScroller.setStackHeight(getExpandedHeight()); 308 updateHeader(); 309 mNotificationStackScroller.updateIsSmallScreen( 310 mHeader.getCollapsedHeight() + mQsPeekHeight); 311 312 // If we are running a size change animation, the animation takes care of the height of 313 // the container. However, if we are not animating, we always need to make the QS container 314 // the desired height so when closing the QS detail, it stays smaller after the size change 315 // animation is finished but the detail view is still being animated away (this animation 316 // takes longer than the size change animation). 317 if (mQsSizeChangeAnimator == null) { 318 mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight()); 319 } 320 } 321 322 @Override 323 public void onAttachedToWindow() { 324 mSecureCameraLaunchManager.create(); 325 } 326 327 @Override 328 public void onDetachedFromWindow() { 329 mSecureCameraLaunchManager.destroy(); 330 } 331 332 private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) { 333 if (mQsSizeChangeAnimator != null) { 334 oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue(); 335 mQsSizeChangeAnimator.cancel(); 336 } 337 mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight); 338 mQsSizeChangeAnimator.setDuration(300); 339 mQsSizeChangeAnimator.setInterpolator(mFastOutSlowInInterpolator); 340 mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 341 @Override 342 public void onAnimationUpdate(ValueAnimator animation) { 343 requestScrollerTopPaddingUpdate(false /* animate */); 344 requestPanelHeightUpdate(); 345 int height = (int) mQsSizeChangeAnimator.getAnimatedValue(); 346 mQsContainer.setHeightOverride(height - mHeader.getExpandedHeight()); 347 } 348 }); 349 mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() { 350 @Override 351 public void onAnimationEnd(Animator animation) { 352 mQsSizeChangeAnimator = null; 353 } 354 }); 355 mQsSizeChangeAnimator.start(); 356 } 357 358 /** 359 * Positions the clock and notifications dynamically depending on how many notifications are 360 * showing. 361 */ 362 private void positionClockAndNotifications() { 363 boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending(); 364 int stackScrollerPadding; 365 if (mStatusBarState != StatusBarState.KEYGUARD) { 366 int bottom = mHeader.getCollapsedHeight(); 367 stackScrollerPadding = mStatusBarState == StatusBarState.SHADE 368 ? bottom + mQsPeekHeight + mNotificationTopPadding 369 : mKeyguardStatusBar.getHeight() + mNotificationTopPadding; 370 mTopPaddingAdjustment = 0; 371 } else { 372 mClockPositionAlgorithm.setup( 373 mStatusBar.getMaxKeyguardNotifications(), 374 getMaxPanelHeight(), 375 getExpandedHeight(), 376 mNotificationStackScroller.getNotGoneChildCount(), 377 getHeight(), 378 mKeyguardStatusView.getHeight(), 379 mEmptyDragAmount); 380 mClockPositionAlgorithm.run(mClockPositionResult); 381 if (animate || mClockAnimator != null) { 382 startClockAnimation(mClockPositionResult.clockY); 383 } else { 384 mKeyguardStatusView.setY(mClockPositionResult.clockY); 385 } 386 updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale); 387 stackScrollerPadding = mClockPositionResult.stackScrollerPadding; 388 mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment; 389 } 390 mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding); 391 requestScrollerTopPaddingUpdate(animate); 392 } 393 394 private void startClockAnimation(int y) { 395 if (mClockAnimationTarget == y) { 396 return; 397 } 398 mClockAnimationTarget = y; 399 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 400 @Override 401 public boolean onPreDraw() { 402 getViewTreeObserver().removeOnPreDrawListener(this); 403 if (mClockAnimator != null) { 404 mClockAnimator.removeAllListeners(); 405 mClockAnimator.cancel(); 406 } 407 mClockAnimator = ObjectAnimator 408 .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget); 409 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator); 410 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 411 mClockAnimator.addListener(new AnimatorListenerAdapter() { 412 @Override 413 public void onAnimationEnd(Animator animation) { 414 mClockAnimator = null; 415 mClockAnimationTarget = -1; 416 } 417 }); 418 mClockAnimator.start(); 419 return true; 420 } 421 }); 422 } 423 424 private void updateClock(float alpha, float scale) { 425 if (!mKeyguardStatusViewAnimating) { 426 mKeyguardStatusView.setAlpha(alpha); 427 } 428 mKeyguardStatusView.setScaleX(scale); 429 mKeyguardStatusView.setScaleY(scale); 430 } 431 432 public void animateToFullShade(long delay) { 433 mAnimateNextTopPaddingChange = true; 434 mNotificationStackScroller.goToFullShade(delay); 435 requestLayout(); 436 } 437 438 public void setQsExpansionEnabled(boolean qsExpansionEnabled) { 439 mQsExpansionEnabled = qsExpansionEnabled; 440 mHeader.setClickable(qsExpansionEnabled); 441 } 442 443 @Override 444 public void resetViews() { 445 mIsLaunchTransitionFinished = false; 446 mBlockTouches = false; 447 mUnlockIconActive = false; 448 mAfforanceHelper.reset(true); 449 closeQs(); 450 mStatusBar.dismissPopups(); 451 mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */, 452 true /* cancelAnimators */); 453 } 454 455 public void closeQs() { 456 cancelAnimation(); 457 setQsExpansion(mQsMinExpansionHeight); 458 } 459 460 public void animateCloseQs() { 461 if (mQsExpansionAnimator != null) { 462 if (!mQsAnimatorExpand) { 463 return; 464 } 465 float height = mQsExpansionHeight; 466 mQsExpansionAnimator.cancel(); 467 setQsExpansion(height); 468 } 469 flingSettings(0 /* vel */, false); 470 } 471 472 public void openQs() { 473 cancelAnimation(); 474 if (mQsExpansionEnabled) { 475 setQsExpansion(mQsMaxExpansionHeight); 476 } 477 } 478 479 public void expandWithQs() { 480 if (mQsExpansionEnabled) { 481 mQsExpandImmediate = true; 482 } 483 expand(); 484 } 485 486 @Override 487 public void fling(float vel, boolean expand) { 488 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); 489 if (gr != null) { 490 gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); 491 } 492 super.fling(vel, expand); 493 } 494 495 @Override 496 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 497 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 498 event.getText().add(getKeyguardOrLockScreenString()); 499 mLastAnnouncementWasQuickSettings = false; 500 return true; 501 } 502 503 return super.dispatchPopulateAccessibilityEvent(event); 504 } 505 506 @Override 507 public boolean onInterceptTouchEvent(MotionEvent event) { 508 if (mBlockTouches) { 509 return false; 510 } 511 resetDownStates(event); 512 int pointerIndex = event.findPointerIndex(mTrackingPointer); 513 if (pointerIndex < 0) { 514 pointerIndex = 0; 515 mTrackingPointer = event.getPointerId(pointerIndex); 516 } 517 final float x = event.getX(pointerIndex); 518 final float y = event.getY(pointerIndex); 519 520 switch (event.getActionMasked()) { 521 case MotionEvent.ACTION_DOWN: 522 mIntercepting = true; 523 mInitialTouchY = y; 524 mInitialTouchX = x; 525 initVelocityTracker(); 526 trackMovement(event); 527 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { 528 getParent().requestDisallowInterceptTouchEvent(true); 529 } 530 if (mQsExpansionAnimator != null) { 531 onQsExpansionStarted(); 532 mInitialHeightOnTouch = mQsExpansionHeight; 533 mQsTracking = true; 534 mIntercepting = false; 535 mNotificationStackScroller.removeLongPressCallback(); 536 } 537 break; 538 case MotionEvent.ACTION_POINTER_UP: 539 final int upPointer = event.getPointerId(event.getActionIndex()); 540 if (mTrackingPointer == upPointer) { 541 // gesture is ongoing, find a new pointer to track 542 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 543 mTrackingPointer = event.getPointerId(newIndex); 544 mInitialTouchX = event.getX(newIndex); 545 mInitialTouchY = event.getY(newIndex); 546 } 547 break; 548 549 case MotionEvent.ACTION_MOVE: 550 final float h = y - mInitialTouchY; 551 trackMovement(event); 552 if (mQsTracking) { 553 554 // Already tracking because onOverscrolled was called. We need to update here 555 // so we don't stop for a frame until the next touch event gets handled in 556 // onTouchEvent. 557 setQsExpansion(h + mInitialHeightOnTouch); 558 trackMovement(event); 559 mIntercepting = false; 560 return true; 561 } 562 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) 563 && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { 564 mQsTracking = true; 565 onQsExpansionStarted(); 566 mInitialHeightOnTouch = mQsExpansionHeight; 567 mInitialTouchY = y; 568 mInitialTouchX = x; 569 mIntercepting = false; 570 mNotificationStackScroller.removeLongPressCallback(); 571 return true; 572 } 573 break; 574 575 case MotionEvent.ACTION_CANCEL: 576 case MotionEvent.ACTION_UP: 577 trackMovement(event); 578 if (mQsTracking) { 579 flingQsWithCurrentVelocity( 580 event.getActionMasked() == MotionEvent.ACTION_CANCEL); 581 mQsTracking = false; 582 } 583 mIntercepting = false; 584 break; 585 } 586 return super.onInterceptTouchEvent(event); 587 } 588 589 @Override 590 protected boolean isInContentBounds(float x, float y) { 591 float yTransformed = y - mNotificationStackScroller.getY(); 592 float stackScrollerX = mNotificationStackScroller.getX(); 593 return mNotificationStackScroller.isInContentBounds(yTransformed) && stackScrollerX < x 594 && x < stackScrollerX + mNotificationStackScroller.getWidth(); 595 } 596 597 private void resetDownStates(MotionEvent event) { 598 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 599 mOnlyAffordanceInThisMotion = false; 600 mQsTouchAboveFalsingThreshold = mQsFullyExpanded; 601 } 602 } 603 604 @Override 605 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 606 607 // Block request when interacting with the scroll view so we can still intercept the 608 // scrolling when QS is expanded. 609 if (mScrollView.isHandlingTouchEvent()) { 610 return; 611 } 612 super.requestDisallowInterceptTouchEvent(disallowIntercept); 613 } 614 615 private void flingQsWithCurrentVelocity(boolean isCancelMotionEvent) { 616 float vel = getCurrentVelocity(); 617 flingSettings(vel, flingExpandsQs(vel) && !isCancelMotionEvent); 618 } 619 620 private boolean flingExpandsQs(float vel) { 621 if (isBelowFalsingThreshold()) { 622 return false; 623 } 624 if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { 625 return getQsExpansionFraction() > 0.5f; 626 } else { 627 return vel > 0; 628 } 629 } 630 631 private boolean isBelowFalsingThreshold() { 632 return !mQsTouchAboveFalsingThreshold && mStatusBarState == StatusBarState.KEYGUARD; 633 } 634 635 private float getQsExpansionFraction() { 636 return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight) 637 / (getTempQsMaxExpansion() - mQsMinExpansionHeight)); 638 } 639 640 @Override 641 public boolean onTouchEvent(MotionEvent event) { 642 if (mBlockTouches) { 643 return false; 644 } 645 resetDownStates(event); 646 if ((!mIsExpanding || mHintAnimationRunning) 647 && !mQsExpanded 648 && mStatusBar.getBarState() != StatusBarState.SHADE) { 649 mAfforanceHelper.onTouchEvent(event); 650 } 651 if (mOnlyAffordanceInThisMotion) { 652 return true; 653 } 654 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f 655 && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded 656 && mQsExpansionEnabled) { 657 658 // Down in the empty area while fully expanded - go to QS. 659 mQsTracking = true; 660 mConflictingQsExpansionGesture = true; 661 onQsExpansionStarted(); 662 mInitialHeightOnTouch = mQsExpansionHeight; 663 mInitialTouchY = event.getX(); 664 mInitialTouchX = event.getY(); 665 } 666 if (mExpandedHeight != 0) { 667 handleQsDown(event); 668 } 669 if (!mQsExpandImmediate && mQsTracking) { 670 onQsTouch(event); 671 if (!mConflictingQsExpansionGesture) { 672 return true; 673 } 674 } 675 if (event.getActionMasked() == MotionEvent.ACTION_CANCEL 676 || event.getActionMasked() == MotionEvent.ACTION_UP) { 677 mConflictingQsExpansionGesture = false; 678 } 679 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0 680 && mQsExpansionEnabled) { 681 mTwoFingerQsExpandPossible = true; 682 } 683 if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN 684 && event.getPointerCount() == 2 685 && event.getY(event.getActionIndex()) < mStatusBarMinHeight) { 686 mQsExpandImmediate = true; 687 requestPanelHeightUpdate(); 688 689 // Normally, we start listening when the panel is expanded, but here we need to start 690 // earlier so the state is already up to date when dragging down. 691 setListening(true); 692 } 693 super.onTouchEvent(event); 694 return true; 695 } 696 697 private boolean isInQsArea(float x, float y) { 698 return (x >= mScrollView.getLeft() && x <= mScrollView.getRight()) && 699 (y <= mNotificationStackScroller.getBottomMostNotificationBottom() 700 || y <= mQsContainer.getY() + mQsContainer.getHeight()); 701 } 702 703 private void handleQsDown(MotionEvent event) { 704 if (event.getActionMasked() == MotionEvent.ACTION_DOWN 705 && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) { 706 mQsTracking = true; 707 onQsExpansionStarted(); 708 mInitialHeightOnTouch = mQsExpansionHeight; 709 mInitialTouchY = event.getX(); 710 mInitialTouchX = event.getY(); 711 712 // If we interrupt an expansion gesture here, make sure to update the state correctly. 713 if (mIsExpanding) { 714 onExpandingFinished(); 715 } 716 } 717 } 718 719 @Override 720 protected boolean flingExpands(float vel, float vectorVel) { 721 boolean expands = super.flingExpands(vel, vectorVel); 722 723 // If we are already running a QS expansion, make sure that we keep the panel open. 724 if (mQsExpansionAnimator != null) { 725 expands = true; 726 } 727 return expands; 728 } 729 730 @Override 731 protected boolean hasConflictingGestures() { 732 return mStatusBar.getBarState() != StatusBarState.SHADE; 733 } 734 735 private void onQsTouch(MotionEvent event) { 736 int pointerIndex = event.findPointerIndex(mTrackingPointer); 737 if (pointerIndex < 0) { 738 pointerIndex = 0; 739 mTrackingPointer = event.getPointerId(pointerIndex); 740 } 741 final float y = event.getY(pointerIndex); 742 final float x = event.getX(pointerIndex); 743 744 switch (event.getActionMasked()) { 745 case MotionEvent.ACTION_DOWN: 746 mQsTracking = true; 747 mInitialTouchY = y; 748 mInitialTouchX = x; 749 onQsExpansionStarted(); 750 mInitialHeightOnTouch = mQsExpansionHeight; 751 initVelocityTracker(); 752 trackMovement(event); 753 break; 754 755 case MotionEvent.ACTION_POINTER_UP: 756 final int upPointer = event.getPointerId(event.getActionIndex()); 757 if (mTrackingPointer == upPointer) { 758 // gesture is ongoing, find a new pointer to track 759 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 760 final float newY = event.getY(newIndex); 761 final float newX = event.getX(newIndex); 762 mTrackingPointer = event.getPointerId(newIndex); 763 mInitialHeightOnTouch = mQsExpansionHeight; 764 mInitialTouchY = newY; 765 mInitialTouchX = newX; 766 } 767 break; 768 769 case MotionEvent.ACTION_MOVE: 770 final float h = y - mInitialTouchY; 771 setQsExpansion(h + mInitialHeightOnTouch); 772 if (h >= getFalsingThreshold()) { 773 mQsTouchAboveFalsingThreshold = true; 774 } 775 trackMovement(event); 776 break; 777 778 case MotionEvent.ACTION_UP: 779 case MotionEvent.ACTION_CANCEL: 780 mQsTracking = false; 781 mTrackingPointer = -1; 782 trackMovement(event); 783 float fraction = getQsExpansionFraction(); 784 if ((fraction != 0f || y >= mInitialTouchY) 785 && (fraction != 1f || y <= mInitialTouchY)) { 786 flingQsWithCurrentVelocity( 787 event.getActionMasked() == MotionEvent.ACTION_CANCEL); 788 } else { 789 mScrollYOverride = -1; 790 } 791 if (mVelocityTracker != null) { 792 mVelocityTracker.recycle(); 793 mVelocityTracker = null; 794 } 795 break; 796 } 797 } 798 799 private int getFalsingThreshold() { 800 float factor = mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f; 801 return (int) (mQsFalsingThreshold * factor); 802 } 803 804 @Override 805 public void onOverscrolled(float lastTouchX, float lastTouchY, int amount) { 806 if (mIntercepting && shouldQuickSettingsIntercept(lastTouchX, lastTouchY, 807 -1 /* yDiff: Not relevant here */)) { 808 mQsTracking = true; 809 onQsExpansionStarted(amount); 810 mInitialHeightOnTouch = mQsExpansionHeight; 811 mInitialTouchY = mLastTouchY; 812 mInitialTouchX = mLastTouchX; 813 } 814 } 815 816 @Override 817 public void onOverscrollTopChanged(float amount, boolean isRubberbanded) { 818 cancelAnimation(); 819 if (!mQsExpansionEnabled) { 820 amount = 0f; 821 } 822 float rounded = amount >= 1f ? amount : 0f; 823 mStackScrollerOverscrolling = rounded != 0f && isRubberbanded; 824 mQsExpansionFromOverscroll = rounded != 0f; 825 mLastOverscroll = rounded; 826 updateQsState(); 827 setQsExpansion(mQsMinExpansionHeight + rounded); 828 } 829 830 @Override 831 public void flingTopOverscroll(float velocity, boolean open) { 832 mLastOverscroll = 0f; 833 setQsExpansion(mQsExpansionHeight); 834 flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled, 835 new Runnable() { 836 @Override 837 public void run() { 838 mStackScrollerOverscrolling = false; 839 mQsExpansionFromOverscroll = false; 840 updateQsState(); 841 } 842 }); 843 } 844 845 private void onQsExpansionStarted() { 846 onQsExpansionStarted(0); 847 } 848 849 private void onQsExpansionStarted(int overscrollAmount) { 850 cancelAnimation(); 851 852 // Reset scroll position and apply that position to the expanded height. 853 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount; 854 if (mScrollView.getScrollY() != 0) { 855 mScrollYOverride = mScrollView.getScrollY(); 856 } 857 mScrollView.scrollTo(0, 0); 858 setQsExpansion(height); 859 requestPanelHeightUpdate(); 860 } 861 862 private void setQsExpanded(boolean expanded) { 863 boolean changed = mQsExpanded != expanded; 864 if (changed) { 865 mQsExpanded = expanded; 866 updateQsState(); 867 requestPanelHeightUpdate(); 868 mNotificationStackScroller.setInterceptDelegateEnabled(expanded); 869 mStatusBar.setQsExpanded(expanded); 870 mQsPanel.setExpanded(expanded); 871 } 872 } 873 874 public void setBarState(int statusBarState, boolean keyguardFadingAway, 875 boolean goingToFullShade) { 876 boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD 877 || statusBarState == StatusBarState.SHADE_LOCKED; 878 if (!mKeyguardShowing && keyguardShowing) { 879 setQsTranslation(mQsExpansionHeight); 880 mHeader.setTranslationY(0f); 881 } 882 setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade); 883 setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade); 884 if (goingToFullShade) { 885 animateKeyguardStatusBarOut(); 886 } else { 887 mKeyguardStatusBar.setAlpha(1f); 888 mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE); 889 } 890 mStatusBarState = statusBarState; 891 mKeyguardShowing = keyguardShowing; 892 updateQsState(); 893 if (goingToFullShade) { 894 animateHeaderSlidingIn(); 895 } 896 } 897 898 private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() { 899 @Override 900 public void run() { 901 mKeyguardStatusViewAnimating = false; 902 mKeyguardStatusView.setVisibility(View.GONE); 903 } 904 }; 905 906 private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() { 907 @Override 908 public void run() { 909 mKeyguardStatusViewAnimating = false; 910 } 911 }; 912 913 private final Animator.AnimatorListener mAnimateHeaderSlidingInListener 914 = new AnimatorListenerAdapter() { 915 @Override 916 public void onAnimationEnd(Animator animation) { 917 mHeaderAnimatingIn = false; 918 mQsContainerAnimator = null; 919 mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater); 920 } 921 }; 922 923 private final OnLayoutChangeListener mQsContainerAnimatorUpdater 924 = new OnLayoutChangeListener() { 925 @Override 926 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 927 int oldTop, int oldRight, int oldBottom) { 928 int oldHeight = oldBottom - oldTop; 929 int height = bottom - top; 930 if (height != oldHeight && mQsContainerAnimator != null) { 931 PropertyValuesHolder[] values = mQsContainerAnimator.getValues(); 932 float newEndValue = mHeader.getCollapsedHeight() + mQsPeekHeight - height - top; 933 float newStartValue = -height - top; 934 values[0].setFloatValues(newStartValue, newEndValue); 935 mQsContainerAnimator.setCurrentPlayTime(mQsContainerAnimator.getCurrentPlayTime()); 936 } 937 } 938 }; 939 940 private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn 941 = new ViewTreeObserver.OnPreDrawListener() { 942 @Override 943 public boolean onPreDraw() { 944 getViewTreeObserver().removeOnPreDrawListener(this); 945 mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight); 946 mHeader.animate() 947 .translationY(0f) 948 .setStartDelay(mStatusBar.calculateGoingToFullShadeDelay()) 949 .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE) 950 .setInterpolator(mFastOutSlowInInterpolator) 951 .start(); 952 mQsContainer.setY(-mQsContainer.getHeight()); 953 mQsContainerAnimator = ObjectAnimator.ofFloat(mQsContainer, View.TRANSLATION_Y, 954 mQsContainer.getTranslationY(), 955 mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight() 956 - mQsContainer.getTop()); 957 mQsContainerAnimator.setStartDelay(mStatusBar.calculateGoingToFullShadeDelay()); 958 mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE); 959 mQsContainerAnimator.setInterpolator(mFastOutSlowInInterpolator); 960 mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener); 961 mQsContainerAnimator.start(); 962 mQsContainer.addOnLayoutChangeListener(mQsContainerAnimatorUpdater); 963 return true; 964 } 965 }; 966 967 private void animateHeaderSlidingIn() { 968 mHeaderAnimatingIn = true; 969 getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn); 970 971 } 972 973 private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() { 974 @Override 975 public void run() { 976 mKeyguardStatusBar.setVisibility(View.INVISIBLE); 977 mKeyguardStatusBar.setAlpha(1f); 978 mKeyguardStatusBarAnimateAlpha = 1f; 979 } 980 }; 981 982 private void animateKeyguardStatusBarOut() { 983 mKeyguardStatusBar.animate() 984 .alpha(0f) 985 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) 986 .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) 987 .setInterpolator(PhoneStatusBar.ALPHA_OUT) 988 .setUpdateListener(mStatusBarAnimateAlphaListener) 989 .withEndAction(mAnimateKeyguardStatusBarInvisibleEndRunnable) 990 .start(); 991 } 992 993 private final ValueAnimator.AnimatorUpdateListener mStatusBarAnimateAlphaListener = 994 new ValueAnimator.AnimatorUpdateListener() { 995 @Override 996 public void onAnimationUpdate(ValueAnimator animation) { 997 mKeyguardStatusBarAnimateAlpha = mKeyguardStatusBar.getAlpha(); 998 } 999 }; 1000 1001 private void animateKeyguardStatusBarIn() { 1002 mKeyguardStatusBar.setVisibility(View.VISIBLE); 1003 mKeyguardStatusBar.setAlpha(0f); 1004 mKeyguardStatusBar.animate() 1005 .alpha(1f) 1006 .setStartDelay(0) 1007 .setDuration(DOZE_ANIMATION_DURATION) 1008 .setInterpolator(mDozeAnimationInterpolator) 1009 .setUpdateListener(mStatusBarAnimateAlphaListener) 1010 .start(); 1011 } 1012 1013 private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() { 1014 @Override 1015 public void run() { 1016 mKeyguardBottomArea.setVisibility(View.GONE); 1017 } 1018 }; 1019 1020 private void setKeyguardBottomAreaVisibility(int statusBarState, 1021 boolean goingToFullShade) { 1022 if (goingToFullShade) { 1023 mKeyguardBottomArea.animate().cancel(); 1024 mKeyguardBottomArea.animate() 1025 .alpha(0f) 1026 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) 1027 .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) 1028 .setInterpolator(PhoneStatusBar.ALPHA_OUT) 1029 .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable) 1030 .start(); 1031 } else if (statusBarState == StatusBarState.KEYGUARD 1032 || statusBarState == StatusBarState.SHADE_LOCKED) { 1033 mKeyguardBottomArea.animate().cancel(); 1034 mKeyguardBottomArea.setVisibility(View.VISIBLE); 1035 mKeyguardBottomArea.setAlpha(1f); 1036 } else { 1037 mKeyguardBottomArea.animate().cancel(); 1038 mKeyguardBottomArea.setVisibility(View.GONE); 1039 mKeyguardBottomArea.setAlpha(1f); 1040 } 1041 } 1042 1043 private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway, 1044 boolean goingToFullShade) { 1045 if ((!keyguardFadingAway && mStatusBarState == StatusBarState.KEYGUARD 1046 && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) { 1047 mKeyguardStatusView.animate().cancel(); 1048 mKeyguardStatusViewAnimating = true; 1049 mKeyguardStatusView.animate() 1050 .alpha(0f) 1051 .setStartDelay(0) 1052 .setDuration(160) 1053 .setInterpolator(PhoneStatusBar.ALPHA_OUT) 1054 .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable); 1055 if (keyguardFadingAway) { 1056 mKeyguardStatusView.animate() 1057 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) 1058 .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) 1059 .start(); 1060 } 1061 } else if (mStatusBarState == StatusBarState.SHADE_LOCKED 1062 && statusBarState == StatusBarState.KEYGUARD) { 1063 mKeyguardStatusView.animate().cancel(); 1064 mKeyguardStatusView.setVisibility(View.VISIBLE); 1065 mKeyguardStatusViewAnimating = true; 1066 mKeyguardStatusView.setAlpha(0f); 1067 mKeyguardStatusView.animate() 1068 .alpha(1f) 1069 .setStartDelay(0) 1070 .setDuration(320) 1071 .setInterpolator(PhoneStatusBar.ALPHA_IN) 1072 .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable); 1073 } else if (statusBarState == StatusBarState.KEYGUARD) { 1074 mKeyguardStatusView.animate().cancel(); 1075 mKeyguardStatusViewAnimating = false; 1076 mKeyguardStatusView.setVisibility(View.VISIBLE); 1077 mKeyguardStatusView.setAlpha(1f); 1078 } else { 1079 mKeyguardStatusView.animate().cancel(); 1080 mKeyguardStatusViewAnimating = false; 1081 mKeyguardStatusView.setVisibility(View.GONE); 1082 mKeyguardStatusView.setAlpha(1f); 1083 } 1084 } 1085 1086 private void updateQsState() { 1087 boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling; 1088 mHeader.setVisibility((mQsExpanded || !mKeyguardShowing) ? View.VISIBLE : View.INVISIBLE); 1089 mHeader.setExpanded(mKeyguardShowing || (mQsExpanded && !mStackScrollerOverscrolling)); 1090 mNotificationStackScroller.setScrollingEnabled( 1091 mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded 1092 || mQsExpansionFromOverscroll)); 1093 mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE); 1094 mQsContainer.setVisibility( 1095 mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE); 1096 mScrollView.setTouchEnabled(mQsExpanded); 1097 updateEmptyShadeView(); 1098 mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded 1099 && !mStackScrollerOverscrolling && mQsScrimEnabled 1100 ? View.VISIBLE 1101 : View.INVISIBLE); 1102 if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) { 1103 mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */); 1104 } 1105 } 1106 1107 private void setQsExpansion(float height) { 1108 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); 1109 mQsFullyExpanded = height == mQsMaxExpansionHeight; 1110 if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) { 1111 setQsExpanded(true); 1112 } else if (height <= mQsMinExpansionHeight && mQsExpanded) { 1113 setQsExpanded(false); 1114 if (mLastAnnouncementWasQuickSettings && !mTracking) { 1115 announceForAccessibility(getKeyguardOrLockScreenString()); 1116 mLastAnnouncementWasQuickSettings = false; 1117 } 1118 } 1119 mQsExpansionHeight = height; 1120 mHeader.setExpansion(getHeaderExpansionFraction()); 1121 setQsTranslation(height); 1122 requestScrollerTopPaddingUpdate(false /* animate */); 1123 updateNotificationScrim(height); 1124 if (mKeyguardShowing) { 1125 updateHeaderKeyguard(); 1126 } 1127 if (mStatusBarState == StatusBarState.SHADE && mQsExpanded 1128 && !mStackScrollerOverscrolling && mQsScrimEnabled) { 1129 mQsNavbarScrim.setAlpha(getQsExpansionFraction()); 1130 } 1131 1132 // Upon initialisation when we are not layouted yet we don't want to announce that we are 1133 // fully expanded, hence the != 0.0f check. 1134 if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) { 1135 announceForAccessibility(getContext().getString( 1136 R.string.accessibility_desc_quick_settings)); 1137 mLastAnnouncementWasQuickSettings = true; 1138 } 1139 if (DEBUG) { 1140 invalidate(); 1141 } 1142 } 1143 1144 private String getKeyguardOrLockScreenString() { 1145 if (mStatusBarState == StatusBarState.KEYGUARD) { 1146 return getContext().getString(R.string.accessibility_desc_lock_screen); 1147 } else { 1148 return getContext().getString(R.string.accessibility_desc_notification_shade); 1149 } 1150 } 1151 1152 private void updateNotificationScrim(float height) { 1153 int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance; 1154 float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance); 1155 progress = Math.max(0.0f, Math.min(progress, 1.0f)); 1156 } 1157 1158 private float getHeaderExpansionFraction() { 1159 if (!mKeyguardShowing) { 1160 return getQsExpansionFraction(); 1161 } else { 1162 return 1f; 1163 } 1164 } 1165 1166 private void setQsTranslation(float height) { 1167 if (!mHeaderAnimatingIn) { 1168 mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation()); 1169 } 1170 if (mKeyguardShowing) { 1171 mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0)); 1172 } 1173 } 1174 1175 private float calculateQsTopPadding() { 1176 if (mKeyguardShowing 1177 && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) { 1178 1179 // Either QS pushes the notifications down when fully expanded, or QS is fully above the 1180 // notifications (mostly on tablets). maxNotifications denotes the normal top padding 1181 // on Keyguard, maxQs denotes the top padding from the quick settings panel. We need to 1182 // take the maximum and linearly interpolate with the panel expansion for a nice motion. 1183 int maxNotifications = mClockPositionResult.stackScrollerPadding 1184 - mClockPositionResult.stackScrollerPaddingAdjustment 1185 - mNotificationTopPadding; 1186 int maxQs = getTempQsMaxExpansion(); 1187 int max = mStatusBarState == StatusBarState.KEYGUARD 1188 ? Math.max(maxNotifications, maxQs) 1189 : maxQs; 1190 return (int) interpolate(getExpandedFraction(), 1191 mQsMinExpansionHeight, max); 1192 } else if (mQsSizeChangeAnimator != null) { 1193 return (int) mQsSizeChangeAnimator.getAnimatedValue(); 1194 } else if (mKeyguardShowing && mScrollYOverride == -1) { 1195 1196 // We can only do the smoother transition on Keyguard when we also are not collapsing 1197 // from a scrolled quick settings. 1198 return interpolate(getQsExpansionFraction(), 1199 mNotificationStackScroller.getIntrinsicPadding() - mNotificationTopPadding, 1200 mQsMaxExpansionHeight); 1201 } else { 1202 return mQsExpansionHeight; 1203 } 1204 } 1205 1206 private void requestScrollerTopPaddingUpdate(boolean animate) { 1207 mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), 1208 mScrollView.getScrollY(), 1209 mAnimateNextTopPaddingChange || animate, 1210 mKeyguardShowing 1211 && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)); 1212 mAnimateNextTopPaddingChange = false; 1213 } 1214 1215 private void trackMovement(MotionEvent event) { 1216 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 1217 mLastTouchX = event.getX(); 1218 mLastTouchY = event.getY(); 1219 } 1220 1221 private void initVelocityTracker() { 1222 if (mVelocityTracker != null) { 1223 mVelocityTracker.recycle(); 1224 } 1225 mVelocityTracker = VelocityTracker.obtain(); 1226 } 1227 1228 private float getCurrentVelocity() { 1229 if (mVelocityTracker == null) { 1230 return 0; 1231 } 1232 mVelocityTracker.computeCurrentVelocity(1000); 1233 return mVelocityTracker.getYVelocity(); 1234 } 1235 1236 private void cancelAnimation() { 1237 if (mQsExpansionAnimator != null) { 1238 mQsExpansionAnimator.cancel(); 1239 } 1240 } 1241 1242 private void flingSettings(float vel, boolean expand) { 1243 flingSettings(vel, expand, null); 1244 } 1245 1246 private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable) { 1247 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight; 1248 if (target == mQsExpansionHeight) { 1249 mScrollYOverride = -1; 1250 if (onFinishRunnable != null) { 1251 onFinishRunnable.run(); 1252 } 1253 return; 1254 } 1255 boolean belowFalsingThreshold = isBelowFalsingThreshold(); 1256 if (belowFalsingThreshold) { 1257 vel = 0; 1258 } 1259 mScrollView.setBlockFlinging(true); 1260 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target); 1261 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel); 1262 if (belowFalsingThreshold) { 1263 animator.setDuration(350); 1264 } 1265 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 1266 @Override 1267 public void onAnimationUpdate(ValueAnimator animation) { 1268 setQsExpansion((Float) animation.getAnimatedValue()); 1269 } 1270 }); 1271 animator.addListener(new AnimatorListenerAdapter() { 1272 @Override 1273 public void onAnimationEnd(Animator animation) { 1274 mScrollView.setBlockFlinging(false); 1275 mScrollYOverride = -1; 1276 mQsExpansionAnimator = null; 1277 if (onFinishRunnable != null) { 1278 onFinishRunnable.run(); 1279 } 1280 } 1281 }); 1282 animator.start(); 1283 mQsExpansionAnimator = animator; 1284 mQsAnimatorExpand = expand; 1285 } 1286 1287 /** 1288 * @return Whether we should intercept a gesture to open Quick Settings. 1289 */ 1290 private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { 1291 if (!mQsExpansionEnabled) { 1292 return false; 1293 } 1294 View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader; 1295 boolean onHeader = x >= header.getLeft() && x <= header.getRight() 1296 && y >= header.getTop() && y <= header.getBottom(); 1297 if (mQsExpanded) { 1298 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y); 1299 } else { 1300 return onHeader; 1301 } 1302 } 1303 1304 @Override 1305 protected boolean isScrolledToBottom() { 1306 if (!isInSettings()) { 1307 return mStatusBar.getBarState() == StatusBarState.KEYGUARD 1308 || mNotificationStackScroller.isScrolledToBottom(); 1309 } else { 1310 return mScrollView.isScrolledToBottom(); 1311 } 1312 } 1313 1314 @Override 1315 protected int getMaxPanelHeight() { 1316 int min = mStatusBarMinHeight; 1317 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD 1318 && mNotificationStackScroller.getNotGoneChildCount() == 0) { 1319 int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount()) 1320 * HEADER_RUBBERBAND_FACTOR); 1321 min = Math.max(min, minHeight); 1322 } 1323 int maxHeight; 1324 if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) { 1325 maxHeight = calculatePanelHeightQsExpanded(); 1326 } else { 1327 maxHeight = calculatePanelHeightShade(); 1328 } 1329 maxHeight = Math.max(maxHeight, min); 1330 return maxHeight; 1331 } 1332 1333 private boolean isInSettings() { 1334 return mQsExpanded; 1335 } 1336 1337 @Override 1338 protected void onHeightUpdated(float expandedHeight) { 1339 if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) { 1340 positionClockAndNotifications(); 1341 } 1342 if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null 1343 && !mQsExpansionFromOverscroll) { 1344 float t; 1345 if (mKeyguardShowing) { 1346 1347 // On Keyguard, interpolate the QS expansion linearly to the panel expansion 1348 t = expandedHeight / getMaxPanelHeight(); 1349 } else { 1350 1351 // In Shade, interpolate linearly such that QS is closed whenever panel height is 1352 // minimum QS expansion + minStackHeight 1353 float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding() 1354 + mNotificationStackScroller.getMinStackHeight(); 1355 float panelHeightQsExpanded = calculatePanelHeightQsExpanded(); 1356 t = (expandedHeight - panelHeightQsCollapsed) 1357 / (panelHeightQsExpanded - panelHeightQsCollapsed); 1358 } 1359 setQsExpansion(mQsMinExpansionHeight 1360 + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight)); 1361 } 1362 mNotificationStackScroller.setStackHeight(expandedHeight); 1363 updateHeader(); 1364 updateUnlockIcon(); 1365 updateNotificationTranslucency(); 1366 if (DEBUG) { 1367 invalidate(); 1368 } 1369 } 1370 1371 /** 1372 * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when 1373 * collapsing QS / the panel when QS was scrolled 1374 */ 1375 private int getTempQsMaxExpansion() { 1376 int qsTempMaxExpansion = mQsMaxExpansionHeight; 1377 if (mScrollYOverride != -1) { 1378 qsTempMaxExpansion -= mScrollYOverride; 1379 } 1380 return qsTempMaxExpansion; 1381 } 1382 1383 private int calculatePanelHeightShade() { 1384 int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin(); 1385 int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin 1386 - mTopPaddingAdjustment; 1387 maxHeight += mNotificationStackScroller.getTopPaddingOverflow(); 1388 return maxHeight; 1389 } 1390 1391 private int calculatePanelHeightQsExpanded() { 1392 float notificationHeight = mNotificationStackScroller.getHeight() 1393 - mNotificationStackScroller.getEmptyBottomMargin() 1394 - mNotificationStackScroller.getTopPadding(); 1395 1396 // When only empty shade view is visible in QS collapsed state, simulate that we would have 1397 // it in expanded QS state as well so we don't run into troubles when fading the view in/out 1398 // and expanding/collapsing the whole panel from/to quick settings. 1399 if (mNotificationStackScroller.getNotGoneChildCount() == 0 1400 && mShadeEmpty) { 1401 notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight() 1402 + mNotificationStackScroller.getBottomStackPeekSize() 1403 + mNotificationStackScroller.getCollapseSecondCardPadding(); 1404 } 1405 int maxQsHeight = mQsMaxExpansionHeight; 1406 1407 // If an animation is changing the size of the QS panel, take the animated value. 1408 if (mQsSizeChangeAnimator != null) { 1409 maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue(); 1410 } 1411 float totalHeight = Math.max( 1412 maxQsHeight + mNotificationStackScroller.getNotificationTopPadding(), 1413 mStatusBarState == StatusBarState.KEYGUARD 1414 ? mClockPositionResult.stackScrollerPadding - mTopPaddingAdjustment 1415 : 0) 1416 + notificationHeight; 1417 if (totalHeight > mNotificationStackScroller.getHeight()) { 1418 float fullyCollapsedHeight = maxQsHeight 1419 + mNotificationStackScroller.getMinStackHeight() 1420 + mNotificationStackScroller.getNotificationTopPadding() 1421 - getScrollViewScrollY(); 1422 totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight()); 1423 } 1424 return (int) totalHeight; 1425 } 1426 1427 private int getScrollViewScrollY() { 1428 if (mScrollYOverride != -1 && !mQsTracking) { 1429 return mScrollYOverride; 1430 } else { 1431 return mScrollView.getScrollY(); 1432 } 1433 } 1434 private void updateNotificationTranslucency() { 1435 float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight()) 1436 / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() 1437 - mNotificationStackScroller.getCollapseSecondCardPadding()); 1438 alpha = Math.max(0, Math.min(alpha, 1)); 1439 alpha = (float) Math.pow(alpha, 0.75); 1440 if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) { 1441 mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null); 1442 } else if (alpha == 1f 1443 && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) { 1444 mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null); 1445 } 1446 mNotificationStackScroller.setAlpha(alpha); 1447 } 1448 1449 @Override 1450 protected float getOverExpansionAmount() { 1451 return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */); 1452 } 1453 1454 @Override 1455 protected float getOverExpansionPixels() { 1456 return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */); 1457 } 1458 1459 private void updateUnlockIcon() { 1460 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1461 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1462 boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance; 1463 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1464 if (active && !mUnlockIconActive && mTracking) { 1465 lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null); 1466 lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150, 1467 mFastOutLinearInterpolator); 1468 } else if (!active && mUnlockIconActive && mTracking) { 1469 lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true, 1470 150, mFastOutLinearInterpolator, null); 1471 lockIcon.setImageScale(1.0f, true, 150, 1472 mFastOutLinearInterpolator); 1473 } 1474 mUnlockIconActive = active; 1475 } 1476 } 1477 1478 /** 1479 * Hides the header when notifications are colliding with it. 1480 */ 1481 private void updateHeader() { 1482 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1483 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1484 updateHeaderKeyguard(); 1485 } else { 1486 updateHeaderShade(); 1487 } 1488 1489 } 1490 1491 private void updateHeaderShade() { 1492 if (!mHeaderAnimatingIn) { 1493 mHeader.setTranslationY(getHeaderTranslation()); 1494 } 1495 setQsTranslation(mQsExpansionHeight); 1496 } 1497 1498 private float getHeaderTranslation() { 1499 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1500 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1501 return 0; 1502 } 1503 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1504 if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) { 1505 return 0; 1506 } else { 1507 return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight; 1508 } 1509 } 1510 return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR; 1511 } 1512 1513 private void updateHeaderKeyguard() { 1514 float alphaNotifications; 1515 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1516 1517 // When on Keyguard, we hide the header as soon as the top card of the notification 1518 // stack scroller is close enough (collision distance) to the bottom of the header. 1519 alphaNotifications = getNotificationsTopY() 1520 / 1521 (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance); 1522 } else { 1523 1524 // In SHADE_LOCKED, the top card is already really close to the header. Hide it as 1525 // soon as we start translating the stack. 1526 alphaNotifications = getNotificationsTopY() / mKeyguardStatusBar.getHeight(); 1527 } 1528 alphaNotifications = MathUtils.constrain(alphaNotifications, 0, 1); 1529 alphaNotifications = (float) Math.pow(alphaNotifications, 0.75); 1530 float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2); 1531 mKeyguardStatusBar.setAlpha(Math.min(alphaNotifications, alphaQsExpansion) 1532 * mKeyguardStatusBarAnimateAlpha); 1533 mKeyguardBottomArea.setAlpha(Math.min(1 - getQsExpansionFraction(), alphaNotifications)); 1534 setQsTranslation(mQsExpansionHeight); 1535 } 1536 1537 private float getNotificationsTopY() { 1538 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1539 return getExpandedHeight(); 1540 } 1541 return mNotificationStackScroller.getNotificationsTopY(); 1542 } 1543 1544 @Override 1545 protected void onExpandingStarted() { 1546 super.onExpandingStarted(); 1547 mNotificationStackScroller.onExpansionStarted(); 1548 mIsExpanding = true; 1549 mQsExpandedWhenExpandingStarted = mQsFullyExpanded; 1550 if (mQsExpanded) { 1551 onQsExpansionStarted(); 1552 } 1553 } 1554 1555 @Override 1556 protected void onExpandingFinished() { 1557 super.onExpandingFinished(); 1558 mNotificationStackScroller.onExpansionStopped(); 1559 mIsExpanding = false; 1560 mScrollYOverride = -1; 1561 if (mExpandedHeight == 0f) { 1562 setListening(false); 1563 } else { 1564 setListening(true); 1565 } 1566 mQsExpandImmediate = false; 1567 mTwoFingerQsExpandPossible = false; 1568 } 1569 1570 private void setListening(boolean listening) { 1571 mHeader.setListening(listening); 1572 mKeyguardStatusBar.setListening(listening); 1573 mQsPanel.setListening(listening); 1574 } 1575 1576 @Override 1577 public void instantExpand() { 1578 super.instantExpand(); 1579 setListening(true); 1580 } 1581 1582 @Override 1583 protected void setOverExpansion(float overExpansion, boolean isPixels) { 1584 if (mConflictingQsExpansionGesture || mQsExpandImmediate) { 1585 return; 1586 } 1587 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 1588 mNotificationStackScroller.setOnHeightChangedListener(null); 1589 if (isPixels) { 1590 mNotificationStackScroller.setOverScrolledPixels( 1591 overExpansion, true /* onTop */, false /* animate */); 1592 } else { 1593 mNotificationStackScroller.setOverScrollAmount( 1594 overExpansion, true /* onTop */, false /* animate */); 1595 } 1596 mNotificationStackScroller.setOnHeightChangedListener(this); 1597 } 1598 } 1599 1600 @Override 1601 protected void onTrackingStarted() { 1602 super.onTrackingStarted(); 1603 if (mQsFullyExpanded) { 1604 mQsExpandImmediate = true; 1605 } 1606 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1607 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1608 mAfforanceHelper.animateHideLeftRightIcon(); 1609 } 1610 } 1611 1612 @Override 1613 protected void onTrackingStopped(boolean expand) { 1614 super.onTrackingStopped(expand); 1615 if (expand) { 1616 mNotificationStackScroller.setOverScrolledPixels( 1617 0.0f, true /* onTop */, true /* animate */); 1618 } 1619 if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1620 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1621 if (!mHintAnimationRunning) { 1622 mAfforanceHelper.reset(true); 1623 } 1624 } 1625 if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1626 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1627 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1628 lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null); 1629 lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator); 1630 } 1631 } 1632 1633 @Override 1634 public void onHeightChanged(ExpandableView view) { 1635 1636 // Block update if we are in quick settings and just the top padding changed 1637 // (i.e. view == null). 1638 if (view == null && mQsExpanded) { 1639 return; 1640 } 1641 requestPanelHeightUpdate(); 1642 } 1643 1644 @Override 1645 public void onReset(ExpandableView view) { 1646 } 1647 1648 @Override 1649 public void onScrollChanged() { 1650 if (mQsExpanded) { 1651 requestScrollerTopPaddingUpdate(false /* animate */); 1652 requestPanelHeightUpdate(); 1653 } 1654 } 1655 1656 @Override 1657 protected void onConfigurationChanged(Configuration newConfig) { 1658 super.onConfigurationChanged(newConfig); 1659 mAfforanceHelper.onConfigurationChanged(); 1660 } 1661 1662 @Override 1663 public void onRtlPropertiesChanged(int layoutDirection) { 1664 if (layoutDirection != mOldLayoutDirection) { 1665 mAfforanceHelper.onRtlPropertiesChanged(); 1666 mOldLayoutDirection = layoutDirection; 1667 } 1668 } 1669 1670 @Override 1671 public void onClick(View v) { 1672 if (v == mHeader) { 1673 onQsExpansionStarted(); 1674 if (mQsExpanded) { 1675 flingSettings(0 /* vel */, false /* expand */); 1676 } else if (mQsExpansionEnabled) { 1677 flingSettings(0 /* vel */, true /* expand */); 1678 } 1679 } 1680 } 1681 1682 @Override 1683 public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) { 1684 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage; 1685 mIsLaunchTransitionRunning = true; 1686 mLaunchAnimationEndRunnable = null; 1687 float displayDensity = mStatusBar.getDisplayDensity(); 1688 int lengthDp = Math.abs((int) (translation / displayDensity)); 1689 int velocityDp = Math.abs((int) (vel / displayDensity)); 1690 if (start) { 1691 EventLogTags.writeSysuiLockscreenGesture( 1692 EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER, lengthDp, velocityDp); 1693 mKeyguardBottomArea.launchPhone(); 1694 } else { 1695 EventLogTags.writeSysuiLockscreenGesture( 1696 EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, lengthDp, velocityDp); 1697 mSecureCameraLaunchManager.startSecureCameraLaunch(); 1698 } 1699 mStatusBar.startLaunchTransitionTimeout(); 1700 mBlockTouches = true; 1701 } 1702 1703 @Override 1704 public void onAnimationToSideEnded() { 1705 mIsLaunchTransitionRunning = false; 1706 mIsLaunchTransitionFinished = true; 1707 if (mLaunchAnimationEndRunnable != null) { 1708 mLaunchAnimationEndRunnable.run(); 1709 mLaunchAnimationEndRunnable = null; 1710 } 1711 } 1712 1713 @Override 1714 protected void onEdgeClicked(boolean right) { 1715 if ((right && getRightIcon().getVisibility() != View.VISIBLE) 1716 || (!right && getLeftIcon().getVisibility() != View.VISIBLE) 1717 || isDozing()) { 1718 return; 1719 } 1720 mHintAnimationRunning = true; 1721 mAfforanceHelper.startHintAnimation(right, new Runnable() { 1722 @Override 1723 public void run() { 1724 mHintAnimationRunning = false; 1725 mStatusBar.onHintFinished(); 1726 } 1727 }); 1728 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right; 1729 if (start) { 1730 mStatusBar.onPhoneHintStarted(); 1731 } else { 1732 mStatusBar.onCameraHintStarted(); 1733 } 1734 } 1735 1736 @Override 1737 protected void startUnlockHintAnimation() { 1738 super.startUnlockHintAnimation(); 1739 startHighlightIconAnimation(getCenterIcon()); 1740 } 1741 1742 /** 1743 * Starts the highlight (making it fully opaque) animation on an icon. 1744 */ 1745 private void startHighlightIconAnimation(final KeyguardAffordanceView icon) { 1746 icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1747 mFastOutSlowInInterpolator, new Runnable() { 1748 @Override 1749 public void run() { 1750 icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, 1751 true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1752 mFastOutSlowInInterpolator, null); 1753 } 1754 }); 1755 } 1756 1757 @Override 1758 public float getPageWidth() { 1759 return getWidth(); 1760 } 1761 1762 @Override 1763 public void onSwipingStarted() { 1764 mSecureCameraLaunchManager.onSwipingStarted(); 1765 requestDisallowInterceptTouchEvent(true); 1766 mOnlyAffordanceInThisMotion = true; 1767 } 1768 1769 @Override 1770 public KeyguardAffordanceView getLeftIcon() { 1771 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1772 ? mKeyguardBottomArea.getCameraView() 1773 : mKeyguardBottomArea.getPhoneView(); 1774 } 1775 1776 @Override 1777 public KeyguardAffordanceView getCenterIcon() { 1778 return mKeyguardBottomArea.getLockIcon(); 1779 } 1780 1781 @Override 1782 public KeyguardAffordanceView getRightIcon() { 1783 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1784 ? mKeyguardBottomArea.getPhoneView() 1785 : mKeyguardBottomArea.getCameraView(); 1786 } 1787 1788 @Override 1789 public View getLeftPreview() { 1790 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1791 ? mKeyguardBottomArea.getCameraPreview() 1792 : mKeyguardBottomArea.getPhonePreview(); 1793 } 1794 1795 @Override 1796 public View getRightPreview() { 1797 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1798 ? mKeyguardBottomArea.getPhonePreview() 1799 : mKeyguardBottomArea.getCameraPreview(); 1800 } 1801 1802 @Override 1803 public float getAffordanceFalsingFactor() { 1804 return mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f; 1805 } 1806 1807 @Override 1808 protected float getPeekHeight() { 1809 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 1810 return mNotificationStackScroller.getPeekHeight(); 1811 } else { 1812 return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR; 1813 } 1814 } 1815 1816 @Override 1817 protected float getCannedFlingDurationFactor() { 1818 if (mQsExpanded) { 1819 return 0.7f; 1820 } else { 1821 return 0.6f; 1822 } 1823 } 1824 1825 @Override 1826 protected boolean fullyExpandedClearAllVisible() { 1827 return mNotificationStackScroller.isDismissViewNotGone() 1828 && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate; 1829 } 1830 1831 @Override 1832 protected boolean isClearAllVisible() { 1833 return mNotificationStackScroller.isDismissViewVisible(); 1834 } 1835 1836 @Override 1837 protected int getClearAllHeight() { 1838 return mNotificationStackScroller.getDismissViewHeight(); 1839 } 1840 1841 @Override 1842 protected boolean isTrackingBlocked() { 1843 return mConflictingQsExpansionGesture && mQsExpanded; 1844 } 1845 1846 public void notifyVisibleChildrenChanged() { 1847 if (mNotificationStackScroller.getNotGoneChildCount() != 0) { 1848 mReserveNotificationSpace.setVisibility(View.VISIBLE); 1849 } else { 1850 mReserveNotificationSpace.setVisibility(View.GONE); 1851 } 1852 } 1853 1854 public boolean isQsExpanded() { 1855 return mQsExpanded; 1856 } 1857 1858 public boolean isQsDetailShowing() { 1859 return mQsPanel.isShowingDetail(); 1860 } 1861 1862 public void closeQsDetail() { 1863 mQsPanel.closeDetail(); 1864 } 1865 1866 @Override 1867 public boolean shouldDelayChildPressedState() { 1868 return true; 1869 } 1870 1871 public boolean isLaunchTransitionFinished() { 1872 return mIsLaunchTransitionFinished; 1873 } 1874 1875 public boolean isLaunchTransitionRunning() { 1876 return mIsLaunchTransitionRunning; 1877 } 1878 1879 public void setLaunchTransitionEndRunnable(Runnable r) { 1880 mLaunchAnimationEndRunnable = r; 1881 } 1882 1883 public void setEmptyDragAmount(float amount) { 1884 float factor = 0.8f; 1885 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 1886 factor = 0.4f; 1887 } else if (!mStatusBar.hasActiveNotifications()) { 1888 factor = 0.4f; 1889 } 1890 mEmptyDragAmount = amount * factor; 1891 positionClockAndNotifications(); 1892 } 1893 1894 private static float interpolate(float t, float start, float end) { 1895 return (1 - t) * start + t * end; 1896 } 1897 1898 public void setDozing(boolean dozing, boolean animate) { 1899 if (dozing == mDozing) return; 1900 mDozing = dozing; 1901 if (mDozing) { 1902 mKeyguardStatusBar.setVisibility(View.INVISIBLE); 1903 mKeyguardBottomArea.setVisibility(View.INVISIBLE); 1904 } else { 1905 mKeyguardBottomArea.setVisibility(View.VISIBLE); 1906 mKeyguardStatusBar.setVisibility(View.VISIBLE); 1907 if (animate) { 1908 animateKeyguardStatusBarIn(); 1909 mKeyguardBottomArea.startFinishDozeAnimation(); 1910 } 1911 } 1912 } 1913 1914 @Override 1915 public boolean isDozing() { 1916 return mDozing; 1917 } 1918 1919 public void setShadeEmpty(boolean shadeEmpty) { 1920 mShadeEmpty = shadeEmpty; 1921 updateEmptyShadeView(); 1922 } 1923 1924 private void updateEmptyShadeView() { 1925 1926 // Hide "No notifications" in QS. 1927 mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded); 1928 } 1929 1930 public void setQsScrimEnabled(boolean qsScrimEnabled) { 1931 boolean changed = mQsScrimEnabled != qsScrimEnabled; 1932 mQsScrimEnabled = qsScrimEnabled; 1933 if (changed) { 1934 updateQsState(); 1935 } 1936 } 1937 1938 public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) { 1939 mKeyguardUserSwitcher = keyguardUserSwitcher; 1940 } 1941 1942 private final Runnable mUpdateHeader = new Runnable() { 1943 @Override 1944 public void run() { 1945 mHeader.updateEverything(); 1946 } 1947 }; 1948 1949 public void onScreenTurnedOn() { 1950 mKeyguardStatusView.refreshTime(); 1951 } 1952 1953 @Override 1954 public void onEmptySpaceClicked(float x, float y) { 1955 onEmptySpaceClick(x); 1956 } 1957 1958 @Override 1959 protected void dispatchDraw(Canvas canvas) { 1960 super.dispatchDraw(canvas); 1961 if (DEBUG) { 1962 Paint p = new Paint(); 1963 p.setColor(Color.RED); 1964 p.setStrokeWidth(2); 1965 p.setStyle(Paint.Style.STROKE); 1966 canvas.drawLine(0, getMaxPanelHeight(), getWidth(), getMaxPanelHeight(), p); 1967 p.setColor(Color.BLUE); 1968 canvas.drawLine(0, getExpandedHeight(), getWidth(), getExpandedHeight(), p); 1969 p.setColor(Color.GREEN); 1970 canvas.drawLine(0, calculatePanelHeightQsExpanded(), getWidth(), 1971 calculatePanelHeightQsExpanded(), p); 1972 p.setColor(Color.YELLOW); 1973 canvas.drawLine(0, calculatePanelHeightShade(), getWidth(), 1974 calculatePanelHeightShade(), p); 1975 p.setColor(Color.MAGENTA); 1976 canvas.drawLine(0, calculateQsTopPadding(), getWidth(), 1977 calculateQsTopPadding(), p); 1978 p.setColor(Color.CYAN); 1979 canvas.drawLine(0, mNotificationStackScroller.getTopPadding(), getWidth(), 1980 mNotificationStackScroller.getTopPadding(), p); 1981 } 1982 } 1983 } 1984