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